There is no good or bad, just fun and not fun

script/myapp_create.pl local::lib
clone
[info]bobtfish
So this post was meant to be a break from the method attributes ranting, and I was going to post about local::lib.

I keep saying just use local::lib and people ask how.. And I reply just write a couple of trivial shell scripts.. And they ask how..

So I set out to provide a canonical example of how which everyone could use.

I also had read but not played with the new --self-contained feature, so I thought that would be fairly awesome. The idea of being able to say 'make installdeps', and have your entire non-core app dependency stack rebuilt was fairly cool.

I then realised, that with dhoss' helper refactor happening, making:

script/myapp_create.pl local::lib

work is totally easy and non painful.

I started this last weekend, as a yak shaving exercise to avoid posting. Didn't prove to be quite as easy as I'd planned.

2 releases of local::lib later, I'm almost there. And when I say almost there, I mean I have a skeleton app on github with some munged scripts to do what I want, make installdeps works, I have code to inject distroprefs patches for various things (I am looking at you WWW::Mechanize), and I can build my work apps from my minicpan + injected internal code.

I haven't actually got as far as patching the preexiting scripts in script/myapp_foo.pl yet, adding 2 lines to Module::Install's Makefile.PL does what I want.

I really haven't got all that many tuits, or clues for hot to get this injected right into the generated application with Module::Install::Catalyst or whatever, so I guess I need to play with that and bug #toolchain with dumb questions some more :)

The next stage is to branch -Devel and start getting this integrated. I currently don't see _any_ solution which doesn't involve inlining my 'env' script into everyone's script/ directory - but I don't feel so guilty about that, as the code will only be executed by people running in local::lib mode, and we'll be pulling the rest of the scripts back into -Runtime soonish anyway I guess leaving them as stubs..

I'm probably likely to ignore this for a few weeks having got it thus far, as I really need to work on super sekrit project, and I have a tonne of work to do to finish the code for the stuff I'm talking about at YAPC, and I'm hoping to have a couple of live production apps deployed with it before that anyway!

So if anyone else would like to see Catalyst have native local::lib support, please please feel free to step up to the plate for anything between making it 'your baby', and just giving the current code a test-drive and docs a brush up, I'd be really grateful about now ;)

P.S. Yes, I fail at ironman, again.

P.P.S. Combining multiple roles with method attributes fails miserably. There are failing tests in my MX::MethodAttributes github fork.

P.P.P.S. I will tell you how to fix it if you care, I don't have time to hack on it.

P.P.P.P.S. Yes, I don't like not having conflict resolution either. It's still better right now than the M.I. mess I was getting into in my controllers...

Why do I always find solving meta problems more fun than real problems.
clone
[info]bobtfish
I have a feeling that I fail at ironman. Meh, oh well - I'll keep blogging anyway, of course - I'm not about to stop ranting about shit for at least a while yet, sorry..

In sympathy for mst, I might have to also make my hair pink (which is the colour I've most often heard mentioned in reference to that), as I fail miserably, and before him...

Oh, no, wait - my hair is pink, DOUBLE FAIL.

Anyway, onto what I was going to rant about.. In the last episode, I was working on Role support for MooseX::MethodAttributes.

This is now released, works, has a much nicer way of asking for it, and has been used in anger by the crazy kids over at MusicBrainz. Go upgrade to 0.12 now. :)

I've still not got round to writing up a really complex example, and/as I think that's actually going to turn into something on github you can play with, and a series of posts...

But for now, here is another trivial use case, which gets messy when you think about it on a larger scale.

Say you work for a company, and you have multiple (different) Catalyst apps, but you want to reuse things and have them 'work the same' between your different apps for consistency.

So in each site, you're going to have a sitewide wrapper template, and it's going to end up like this:

wrapper = wrapper || 'site/layouts/full.tt2'

which is okish. This will use the wrapper specified in your code, or the default wrapper..

But how about if you want to skin your site, or do a/b testing with two different site layouts, or..

Having that chunk of string buried in your template is kinda rubbish now, huh?

So you write this:

wrapper = wrapper || default_wraper

And then you write this (some context shown):

package MyCompany::MyApp::Controller::Root;
use Moose;

BEGIN { extends 'Catalyst::Controller' }

sub root : Chained('/') PathPart('') CaptureArgs(0) {
    my ($self, $c) = @_;
    # N.B. This is the only line added, rest of code was already there ;)
    $c->stash->{default_wrapper} = $c->view('HTML')->default_wrapper;
    # More stuff to do for _every_ page here.
}
...
package MyCompany::MyApp::View::TT;
use Moose;

extends 'Catalyst::View::TT';

has default_wrapper => ( is => 'ro', required => 1, isa => 'Str' );

...
<MyApp::View::TT>
   default_wrapper site/layouts/full.tt2
</MyApp::View::TT>


And suddenly the configuration value is in config. Woohoo, you're now correctly design pattern compliant, as you're not storing app config data in the templates :)

Now, back to the point - your company has a lot of apps.. They're all going to do this. So what, it's one line of code. WRONG

DON'T REPEAT YOURSELF

DON'T REPEAT YOURSELF. (Look, I fail! Oh, we covered that once..)

This won't be the only commonality, remember, this is a trivial example so lets think up a way of doing this generically, so we don't copy code around. The traditional way to abstract that would be to provide a 'MyCompany' base class with the default 'sub root'.

Ok, that's cool - except you suddenly realise that you have 6 features like this, but if you add all 6 to all of your apps, it breaks things.

FAIL

Whatcha gonna do?

package MyCompany::ControllerBase::Root;
use Moose;

BEGIN { extends 'MyCompany::ControllerBase'; }

sub root : Chained('/') PathPart('') CaptureArgs(0) {
    my ($self, $c) = @_;
    if ($c->config->{features}{has_feature_1}) {
        # Do stuff for feature one
    }
    if ($c->config->{features}{has_feature_2}) {
        # Do stuff for feature two
    }
    # Repeat, 4 more times
}

Sorry, you fail at OO. Next.

package MyCompany::ControllerBase::Root::FeatureOne;
use Moose;

sub root : Chained('/') PathPart('') CaptureArgs(0) {
    my ($self, $c) = (shift, shift);
    $c->stash->{default_wrapper} = $c->view('HTML')->default_wrapper;
    $self->next::method($c, @_);
}
... Repeat, 5 more times

I laugh the special laugh I save for people who think that having 6 base classes is a good idea, and we move on now.

package MyCompany::CatalystX::FeatureOne;
# Code may contain up to one lie, see if you can spot it before you read the explanation.
use strict;
use warnings;

sub import {
    my $caller = caller();
    no strict 'refs';
    &{$caller."::_add_feature"}(sub {
        my ($self, $c) = @_;
        $c->stash->{default_wrapper} = $c->view('HTML')->default_wrapper;
    });
}

package MyCompany::ControllerBase::Root;
use Moose;

BEGIN { extends 'MyCompany::ControllerBase'; }

{
    my @features;
    sub _add_feature { push @features, shift }
    sub _run_features { $_->(@_) for @features }
}

sub root : Chained('/') PathPart('') CaptureArgs(0) {
    my ($self, $c) = @_;
    _run_features($self, $c);
}

package MyCompany::MyApp::Controller::Root;
use Moose;

BEGIN { extends 'MyCompany::ControllerBase::Root'; }

Wow. Note the crack fueled non-generic implementation of part of method modifiers and part of roles. Note also, this doesn't actually work in your base class as shown because it relies on the caller. Fairly easy to fix with more code..

The fact that it doesn't work yet and that you can't use namespace::clean (without an 'avoid the mad shit' incantation) just adds to the charm.. ->can('has'). No, really I actually hate this least so far.

So anyway, after wallowing in filth. Let me present:

package MyCompany::CatalystX::FeatureOne;
use Moose::Role;
use namespace::autoclean;

requires 'root';

after 'root' => sub {
    my ($self, $c) = @_;
    $c->stash->{default_wrapper} = $c->view('HTML')->default_wrapper;
};

package MyCompany::CatalystX::DefaultRoot;
use Moose::Role -traits => 'MethodAttributes';

# Yes, I need no body, all the functionality is in modifiers
sub root : Chained('/') PathPart('') CaptureArgs('') {} 

package MyCompany::MyApp::Controller::Root;
use Moose;

BEGIN { extends 'MyCompany::MyApp::ControllerBase' }

with map { "MyCompany::CatalystX::ControllerRole::$_" } qw/
    DefaultRoot
    Feature1
    Feature2
    Feature3
/;

Tada, much more elegant.

And you can split it how you like - have the features which really are common for all apps in your MyCompany base class, sure. Have the action in the base class and just apply modifiers to it, sure.

Whatever works for you, helps you write cleaner code and allows you to avoid repeating yourself - it's all good with me, I'm just pointing out how taking advantage of MooseX::MethodAttributes to do something else that can be done in 1 line might actually be a good option for maintainable and reusable application code.

Whoda thunked that?

Controller actions inside Moose roles - possible right now!
clone
[info]bobtfish
package MyApp::Item;
use Moose::Role -traits => 'MooseX::MethodAttributes::Role::Meta::Role';
use namespace::autoclean;

sub view : Chained('item') PathPart('') Args(0) {}

sub edit : Chained('item') PathPart('edit') Args(0) {}

package MyApp::Controller::Foo;
use Moose;
use namespace::autoclean;

BEGIN { extends 'Catalyst::Controller' }

with 'MyApp::Item';

sub item : Chained('/') PathPart('foo') CaptureArgs(1) {}

This simple example will produce the following URIs (corrected by an astute reader):
  • /foo/item/*
  • /foo/item/*/edit

The sweet thing about this is that you can apply the role anywhere you have a 'sub item', and as long as the item is stored in the same place in the stash, and can be manipulated in expected ways (e.g. is a dbic resultset)..

You've suddenly got generic, re-useable CRUD, without any nasty multiple inheritance going on, method modifiers should work like you expect, etc.

Nice, eh? I have lots of places where I like to re-use chunks of URL space like this in my apps, and currently I have to use steeenking multiple inheritence.

I just CPAN'd a dev release of MooseX::MethodAttributes which makes this work.

Caveat: I'm fairly sure that composing roles with actions in them onto other roles will not work correctly currently, it may work, but I haven't written tests yet so it probably doesn't.

I'll be fixing this and releasing 'for real' within the next week or so hopefully, but I'm fairly sure it's solid as long as you compose roles directly onto classes as shown above. Method modifiers (not shown above) are also tested and do work.

P.S. I'm fairly sure MooseX::MethodAttributes::Role::Meta::Role is the worst module name evar, suggestions welcome..

P.P.S.It was pointed out to me that what I'm doing above could previously have been trivially achieved using controller base classes. This is totally true, as it was a simple example. I'll demonstrate a much more complex example and how having to use base classes and multiple inheritance make this a lot more painful than it has to be for more complex cases in a future post :)

This week in Catalyst.
clone
[info]bobtfish
We're almost ready to release 5.80004, 'Unknown error' is verified killed, and I just need to tweak some docs..

Chris just CPANed the first version of Catalyst::Engine::STOMP, and we're sitting down on Monday night for a hacking session on that, which will be cool.

In other news, there has been quite a lot of discussion about what we're going to do to kill off the hated attribute syntax, and there's now some prototype code on github..

I don't have much to report other than that - lots of birthdays & an attack of the lazy has meant I haven't actually achieved so much in any of my other projects this week. Some dumb tourist ran out into the road in front of me last night, causing me to wipe out in a fairly epic manor (graphic photo), so I guess I won't be out riding tomorrow...

t0m->can('has')->beer == Acme::LOLCat->can't('has')
clone
[info]bobtfish
So, CPAN, it has some awesome code. Some shitty code. Some plain silly code.

All the deliberately silly code (and programmers love daft in-jokes, so there is a lot of silly code) in perl which is on CPAN, lives in the Acme:: namespace.

Ergo Acme::LOLCat, and, if you're the web application type, U CAN B RERITIN UR OUTPUTZ.

Aaaaand, back in the real world...

J Random programmer, writing real code, says:

package MyCompany::PlusOneClass;
use Moose;
use MooseX::Types::Moose qw/Int/;

has id => ( isa => Int, is => 'ro', required => 1 );

sub id_plus_one {
    my ($self) = @_;
    return $self->id + 1;
}

And all is well in the world.

Except, try this shit:

use Test::More tests => 4;

my $class = 'MyCompany::PlusOneClass';
my $instance = $class->new( id => 42 );

my $id_meth = $instance->can('id');
my $id_p1_meth = $instance->can('id_plus_one');

is $instance->$id_meth(), 42;
is $instance->$id_p1_meth(), 43;

# All good so far, see
# http://www.shadowcat.co.uk/blog/matt-s-trout/madness-with-methods/
# if you don't grok the stuff with ->can

ok $class->can('has'); # We are NOT Acme::LOLCat, ergo this is NOT cool.

# How many attributes?
my $attr_count = scalar( $class->meta->get_attribute_list );

$instance->has('unused', is => 'ro'); # You didn't really mean that, did you?

# This is some fucked up shit, right here..
isnt scalar( $class->meta->get_attribute_list ), $attr_count;

So, yeah.. Leaving keywords about can ruin your life, quite badly. Go talk to the #reaction crew for actual real life war stories about how random crap in your namespace murders kittens.

The correct solution to this is to drink the rafl koolaid. Just say use namespace::autoclean, and your class cannot has. Super.

So, this comes up fairly often on irc. The good way to explain this all in brief:

ok Acme::LOLCat->can('has');
ok !YourClass->can('has');

Clear? Good. I'm glad. I like that explanation too.

LAST WEEKEND I FOUND A HUGE BUG.

OH NOES!

To try and bleach my brain somewhat more, so I could forget how all software sucks & etc (my hopes, dreams and illusions having been shattered by above bug), I went out to #london.pm beers tonight.

Whereupon, the #london.pm people proved that they are epic pedants in real life, as well as on the mailing list.

ok !Acme::LOLCat->can('has');

is not acceptable.

Thus: Acme::LOLCat->can't('has') is yours for the taking.

PATCHES WELCOME, HTH, KTHNX BAI.

Catalyst 5.80003 and French Perl workshop.
clone
[info]bobtfish
I fixed MooseX::MethodAttributes, and we subsequently shipped shipped Catalyst 5.80003, with working method modifiers on controller actions. I also wrote the docs great success!

I fail at sekrit project and Authentication::Credential::HTTP (sorry abraxxa!)

So, iron man readers who among the brits is going to FPW? I am, as is Chris!

Eurostar is still cheap, do it. Dooo itttt. :)

First post (in ages)
clone
[info]bobtfish
Hi anyone still reading ;)

So blogging about my life and random crap has died a fair death, and I'm not really interested in starting again. However, I'm doing enough Open Source stuff that there's plenty to write about there, so I'm going to steal this account for blogging about that sort of stuff - mst having got the Iron Man (rant) thing off the ground (finally) has given me the appropriate kick in the pants to start.

This is most likely to be a series of lists of what I've been hacking on, and link love to other people's shit I find interesting, I don't see myself having time for massive essays generally - therefore, apologies in advance..

So, if you're not interested in perl or Catalyst, then you should probably de-friend me now..

On my Catalyst 5.8 list this week, mostly the third 90%, all of which I planned but failed to get done this weekend:


Phew, that's fairly stacked up, doubt I'll get through it all, especially as I've been totally neglecting my super sekrit project recently, and actually need to do some serious hacking on that also..

Signing off, as I'm now late for work (even given Monday morning). :/

Home