20 posts tagged “moose”
Well Perl Ironman Readers,
Based on the public and private comments from the previous blog I decided to go ahead and write something (I hope) cpan worthy. It should show up shortly over here but until then you can check it out on github (or clone / offer improvements)
I'm not in love with the name. If you have a better idea please speak up, but I don't like to let naming issues stop me from publishing.
Thanks!
I find as part of my normal course of writing perl moose based code, I do the following quite often:
Basically I tend to use the above with a core application class that is delegating work to several other classes. In the above case, my general Library Class contains a Album class (presumable your library would hold an album and perhaps several other things). In my Application logic I'm going to be calling methods on Album which have been properly normalized to the class which attempt to encapsulate the problem domain it attempts to solve.
I find the above form, although a bit verbose, to give me a lot of flexibility, particularly when I bring the application model into Catalyst via something like Catalyst::Model::Adaptor, which let's me define all the args in the global catalyst configuration. Also, for testing, it's nice to be able to override the default classes, swapping in say a Mock Object or similar for the purposes of test cases.
In general use this construct commonly to avoid hardcoding all the connections between the elements of my applications.
Usage might be like:
Although I as mentioned, typically I instantiate this via Catalyst or as part of a more complicated application using an inversion of control container like Bread::Board.
However it can lead to a little too much verbosity in core application classes, particularly if I have a bunch of these. It's a type of repeated line noise which hides the actual functionality of the class. So I'm considering creating a Moose Parameterized Role to build these for me, something that would work like (as a minimal case):
Useful? What, if anything should I do to make this CPAN worthy? Or are the requirements of this oft repeated task unique enough as to make a usable code generator unfeasible? Or am I the only one doing this (I don't need vanity CPAN modules...)
If you say "Yes", you also owe me a reasonable name (naming things I am not good at...)
So in my attempt to add in some support for Dependent Type programming to Perl Moose programming I'm at the part where I need to integrate this into declaring your Moose based Perl objects. To recap, with a Dependent Type Constraint you can declare a constraint like:
subtype Range,
as Dict[max=>Int, min=>Int],
where {
my $range = shift @_;
return $range->{max} > $range->{min};
};
subtype RangedInt,
as Dependent[Int, Range],
where {
my ($int,$range) = @_;
return ($int >= $range->{min} && $int <= $range->{max});
};
RangedInt([max=>10, min=>1])->check(5); ## Is fine
RangedInt([max=>10, min=>1])->check(15); ## Is NOT fine
You can then use this in a Perl class like so:
class MyClass {
has young_adult_age => ( is=>'ro', isa=>RangedInt[min=>18,max=>34] );
}
And you can instantiate this like so:
MyClass->new(young_adult_age=>20);
But the following will throw an exception:
MyClass->new(young_adult_age=>40);
Also, if you are using MooseX::Declare you can use these in your method body signatures:
method height(RangedInt[min=>10,max=>100] $height) { ## Do something here }
All this is supported in the current repository version you can review here.
So far so good. However, it's been requested to be able to fulfill a dependency requirement via $self when using dependent types in a class. My proposed syntax for this is to make the 'isa' and 'does' attribute options similar to default, where if the attribute is lazy and the value is a coderef, we de-reference it and pass $self. So, for example, let's say you create some type constraints and a class that uses it like so:
use Set::Scalar;
use Email::Valid;
subtype Email,
as Str,
where {
return Email::Valid->address($_);
};
subtype Set,
as Dependent[class_type 'Set::Scalar', Any],
where {
my ($set, $item) = @_;
foreach my $element ($set->members) {
return unless $item->check($element);
} 1
}
subtype UniqueEmail,
as Dependent[Email, Set],
where {
my ($email, $set) = @_;
return !$set->has($email);
}
class Person {
has email_set => (is=>'ro', isa=>Set[Email] );
has email => (is=>'ro', isa=>UniqueEmail[Set[Email]] );
}
Ideally, when you try to instantiate this class, the attribute 'email' would be bound to attribute 'email_set', in such a way that it's value should be unique in that set. To achieve this, I am proposing an attribute trait that allows something like:
class Person {
has email_set => (is=>'ro', isa=>Set[Email] );
has email => (
is=>'ro',
isa=>sub {
my $self = shift @_;
my $set = $self->email_set;
return UniqueEmail[$set];
},
traits=>['Dependent'],
);
}
And at validate time, the coderef would be dereferenced with $self, similarly to the way the default option works. We'd do the same for the 'does' attribute. And to make it even more similar to default, I'd support coderefs here without lazy, which would not pass $self, but would execute at runtime.
For myself, I'd rather have another solution, but maybe I just don't fully grasp the use cases. Comments on the above proposals very welcome. However, if you are wanting something very different, you should give me enough specification for me to get it.
==UPDATES==
It's been suggested that instead of overloading 'does' and 'isa', to use a new options, such as 'generate_isa' or similar. Should be more sane and play nicer with other attributes that expect isa and does to behave a certain way.
One thing which has come up from time to time on the Moose Type Constraint wish list is the ability to more easily define type parameters. For example, it would be great if we could very simple create an Integer type that is bound by a range. For example:
subtype Range,
as Dict[max=>Int, min=>Int],
where {
my $range = shift @_;
return $range->{max} > $range->{min};
};
subtype RangedInt,
as Dependent[Int, Range],
where {
my ($int,$range) = @_;
return ($int >= $range->{min} && $int <= $range->{max});
};
class MyClass {
has young_adult_age => ( is=>'ro', isa=>RangedInt[min=>18,max=>34] );
}
And you can instantiate this like so:
MyClass->new(young_adult_age=>20);
But the following will throw an exception:
MyClass->new(young_adult_age=>40);
This gives your Moose based Perl programming a bit more flexibility!
Right now this is not on CPAN, but you can see it, play with it (and help me shed light on any of the corner cases) at: https://jules.scsys.co.uk/gitweb/gitweb.cgi?p=gitmo/MooseX-Dependent.git;a=tree
At this point the code is shaping up, although there's a bit of ugly stuff in the type constraint code. Even coercions work as you might hope.
Pending responses to the above syntax, remaining things before CPAN is primarily some attribute traits so that we can expose $self to a dependent type in a class. This would allow something like:
class Person {
has people => (is=>'ro', isa=>PeopleResultSet, required=>1);
has id => (is=>'ro', is=>UniqueID[PeopleResultSet], traits=>['Dependent']);
}
The idea being that the attribute 'id' would be bound to the attribute 'people' so that you could not create a new Person that had a pre-existing ID. The attribute trait stuff is still pending and feedback, test cases / use cases and of course code are very welcome.
UPDATED: My apologies for rebranding the date to anyone who has a working aggregator, but seems that whatever the settings for the Ironman aggregator, it wasn't picking up my post. Maybe I'm supposed to say, "Perl Programming now..."
10. It's Getting Great Press!
I can't remember the last time a Perl module has generated so much discussion, whether in blogs, at conferences or just in general. People who use it nearly always become advocates. Here's a few I was reading just recently:
- http://blog.jrock.us/articles/Myth:%20Moose%20is%20an%20unnecessary%20dependency.pod
- http://avatraxiom.livejournal.com/70947.html
- http://transfixedbutnotdead.com/2008/03/12/doodling-with-moose-part-1/
You would not have to look hard to find many, many more.
9. Makes Creating Objects Fast an Easy!
Whether you are using 'classic' Moose or playing with the new MooseX::Declare I have never used an object system that was so easy and felt like it belonged to Perl. In the best tradition of Perl it makes easy things easy to do, and make the impossible doable. All this with minimal boilerplate.
Not convinced? Check out this comparison of all the stuff you need to write to make a non Moose Perl object approach the functionality of what you get out of the box with Moose.
8. Makes Perl Objects Powerful!
Moose is built on top of a well researched and highly respected Meta Object Protocol which means Moose objects and the framework and software ecosystem being built around it are on very firm foundations. We've been developing on and in Moose for more than three years now and the concepts underlying it are not showing signs of weakness. Usually by now people would be talking of a 'rewrite to get it right' but clearly Moose is pretty right as it is. As a result we can focus our attention on expanding MooseX modules and on advocacy and documentation. Clearly, Moose is the most powerful object system for Perl and is a heavyweight in comparison to other languages with strong object oriented features.
7. Moose Attributes
Removing all the boilerplate associated with creating object attribute reader/writer methods, as well as making it easy to apply type constraints (see for more) was probably the thing that got me interested in Moose to begin with. If Moose only did this, it would still be useful. That fact that this feature is just a small one among many is mind blowing.
6. Moose Baked in Type Constraints and Coercions
Although I've always loved that Perl is a dynamic language, I've often wished it was easier to validate arguments and attributes. There's probably been a dozen or more such systems on CPAN, but Moose Type Constraints mixes the perfect blend of usefulness and wide adoption that met my immediate need while growing with me as a developer. Now I'm a Type Constraint junkie, who spends him time researching more and more esoteric type constraint concepts.
5. Moose Roles are Awesome!
I could say more, but someone smarter has recently done a blog series about Moose Roles that I highly recommend you check out.
4. MooseX
Do a search for "MooseX" on CPAN (here for example) and you are going to get a ton of useful things. This points out not only the energy and excitement of the community, but serves as more proof that Moose was 'done right' the first time. the For a list of recommended MooseX modules see this article.
3. Great Moose Inspired Stuff!
There's more and more stuff showing up on CPAN that is Moose inspired. For example, the recent Catalyst port has revitalized development on this important web application project. Or check out KiokuDB if you are looking for a fast and easy way to persistent your objects. If you are interested in a cutting edge user interface and interface modeling system, see Reaction. I've only scratched the surface here.
2. Documentation, Tests and Online Tutorials.
When I got to Moose, I found there was not only reasonable documentation to help with with the basics, but also a straightforward tutorial that showed how to go about building applications the Moose way. It definitely helped me to grasp the essential bits and it got me started very quickly on the features I found most useful and time saving. For me this was the key to adopting Moose and the online docs really show you the heavy benefits right off the top.
When I needed more examples, I could go straight to the test directory and find scores of useful tests that showed how the system worked as well as provided me very useful code samples.
And the Number One Great Thing about Moose...
1. The Community!
Not only would all the above not be possible without the tireless and creative effort of the group of core developers, but this community has really reached out and brought lots of people into the fold. They are greatly responsive to bug reports and feature requests, maintaining a pace of releases that is truly amazing. This community also has some of the smartest people I've ever had the pleasure to work with. I definitely feel being part of the Moose community has not only made me a better programmer, but a better person as well.
- Mailing List Info
- IRC Chats (Join Channel #moose on irc.perl.org)
Last night (NYC time at least) I was able to have a great brainstorming conversation with rafl, confound and others regarding new ways to declare Catalyst Actions. This conversation was set off by a blog post and my response.
A few things came up in the conversation that warranted me stepping back and pondering the best way to clarify my thoughts and my position. The first has to do with my feelings about how controllers and actions should require minimum (or ideally no) additional syntax knowledge, above what MooseX::Declare and Moose expects, in order for a developer to be productive. This is what I meant by 'Controller Baby Talk', and I think it's important since we have a goal of making it easy to get started and perform the common cases. It's related to the reason why I think query and body parameters should be expressed in the method signature as well. The idea is less method calls, more declaration and more useful defaults. Someone just starting should be able to make decent controllers off the top with a minimum of learning. They might not make great controllers, but they will make workable controllers.
The second issue has to done with Catalyst Chained Actions which I refer you to the link if you are not familiar. Chained actions are great in that they help reduce a lot of repeated code, as well as having a very elegant feel to them. However, in practical life I've not found using them to be very beginner friendly. I've even seen intelligent, intermediate and above level developers struggle with them. I hesitate to criticise here, since I'm not contributing code to try and solve these problems (yet) but in general my experience with Chained actions leads me to conclude that they belong in the hands of at least intermediate or higher programmers, people with the experience to realize they need more and the ability to go learn how to do it correctly. I realize you may feel differently, so please aim your thoughts into comments. I'm saying this based on my experiences as a team lead building a large Catalyst driven website where I spent too much time struggling to help other developers get their chained actions working correctly, as well as debugging trouble with using $c->uri_for against action chains.
Based on this, I left chained actions out of my discussion. I do think whatever we create to replace the current method attribute system must address the need to make Chained actions work and work better, I just think it's not a button I want newcomers to the framework to have access to on day one. That's why I originally left out any discussing of chained actions out of my blog post.
Based on the IRC chat and the feedback I've recieved, I think that you should be able to easily 'scale up' to chained actions as you need them. In particular, I like the idea, mentioned a few times in last night's chat, that all actions are reall chained actions, just some are very short chains. Under this thinking, everything is an action chain, but we have some shortcut types to enable some simple cases (such as what the 'Path' attribute is now used for). These special cases are just shortcuts that behind the curtain uses chaining. In this case we'd all be using chained actions all the time, but for the simple cases the work would be hidden and the end developer doesn't even know it, until they rise in skill and have that wonderful, "AH HA!" moment of realization! I can see that could make our underlying code base more clean, as well as remove a lot of special case processing. As a side effect, it would probably make action chains more robust, and probably solve some my concerns. That of course would require a major rewrite of the dispatcher code, which is something for another blog.
However, I think we should be careful to move Catalyst controllers too far away from plain old Perl / Moose classes. Although I don't love the method attribute syntax of the current system, one thing I do love is that Catalyst Controllers are just plain classes with a bit of decoration. That makes it easy for me to apply all my knowledge regarding designing and developing classes to Catalyst Controllers. It's something I would not want to see disappear.
Given this idea, I want to offer another thought experiment that would outline the growth from simple case controllers up to chained controllers, trying to follow the intelectual growth of a new developer. Again, I want to keep the new syntax to a minimum. Some of the syntax here comes out of the IRC conversation I've mentioned, but no code is written and many more use cases need to be written and expressed before we can clarify the specification.
Let's start with a cleaned up version of the controller I from my last post. This is a controller that offfer URI endpoints to view a Gallery of People, see details on a particular person (by ID or Email) and create a new person. Let's assume the Person entity has the following attributes: ID, Name, Birthdate, Email with ID and Email as unique and all fields are required. Let's also for the moment assume that ID is auto generated by the storage system, such as a database. I think a webpage like this is a pretty common ask for a developer, and many websites are just more complex versions of this simple case.
use Catalyst::Declare::Controller;
class People {
method gallery($ctx: Int :$page = 1, Int :$rows = 1) {
my $people_rs = $ctx->
model('People')->
search({},{page=>$page, rows=>$rows});
$ctx->stash(
people_rs = $people_rs,
);
}
multi method person($ctx: Int $id) {
if(my $person = $ctx->model('People')->find(id=>$id)) {
$ctx->stash(person=>$person);
} else {
$ctx->forward('not_found', ["Person of id: $id doesn't exist"])
}
}
multi method person($ctx: Email $email) {
if(my $person = $ctx->model('People')->find(email=>$email)) {
$ctx->stash(person=>$person);
} else {
$ctx->forward('not_found', ["Email: $email doesn't exist"])
}
}
method create($ctx: Dict[name=>Str, email=>Email, birthdate=>DateTime] $post) {
my $validated_post = $ctx->model('ValidatePersonCreatePost')->validate($post);
if($validated_post->is_valid) {
$ctx->model('People')->create($validated_post);
$ctx->forward('CreatedNewPerson', ["I created person with $validated_post"]);
} else {
$ctx->stash(template=>'people/create_has_errors');
}
}
method prepare_database(HashRef $opts) {
## just a plain old method, move along
}
}
I added a bit more of the kind of logic you might expect to see in this kind of controller. For this case, I assumed we are storing all our data in DBIC, so I used that syntax. However, I want to point out it's not good practice to put this kind of logic in your controllers! I'm just doing it this way to make it easier for you to follow along and I will offer one last version at the end which will reflect this best practice. This will make it easier later on to see the value of chained actions as well. This controller would map the following URLs:
{hostname}/people/gallery?page={page}&rows={rows}
{hostname}/people/person/{id}
{hostname}/people/person/{email}
{hostname}/people/create
Remember, we suggested in the previous blog that if the last argument in the signature is a reftype, such as something inheriting from ArrayRef, HashRef or even Object, we assume this action requires HTTP Post and attempts to coerce the post entity against the given type constraint.
For the purposes of clarity, I skipped all the MooseX::Types imports, just assume they are there and that useful coercions exists, such as something to parse strings into DateTime objects and something to take a urlencoded post entity and convert it to a HashRef. For the purposes of making it easier for newcomers, we might even wish to consider automatically importing a bunch of the most common case MooseX::Types and coercions.
One thing that I changed from the last example is I've removed the 'action' keyword, relying exclusively on 'method' which we get from MooseX::Declare. Basically what I am saying here is that an action is just a method that expects to be part of a context, so just requiring "$ctx:" in the method signature invocant should be enough to tell the underlying system this is an action, not an ordinary method. I think this could be more intuitive to a newcomer, saying that an action is just a method with a context. Okay, so that's not the entire story, but again, it's still a pretty big gulp for a new developer. She can start with that and be very productive with just that amount of knowledge.
I also swapped 'controller' for 'class' which again comes straight out of the MooseX::Declare syntax. Right now I am not seeing what calling this controller is buying us, except that it a new thing to learn. Right now it feels gratuitiously different.
So right now you'd have a workable controller and you didn't have to learn anything above and beyond what Moose and MooseX::Declare expect. I expect you are looking at this and thinking, "Hmm, if we had chained actions, we'd be able to reduce a lot of this repeated model calls." Also, what happens when you need more complex controllers? Let's say the developer gets a request to add the ability to both edit and delete individual Persons within the set of People? Again, this is a classic argument for Chained actions, since we can have much clearer code. For the moment though, let's stay with the current knowledge set and see what our developer might write.
use Catalyst::Declare::Controller;
class People {
method gallery($ctx: Int :$page = 1, Int :$rows = 1) {
my $people_rs = $ctx->
model('People')->
search({},{page=>$page, rows=>$rows});
$ctx->stash(
people_rs = $people_rs,
);
}
multi method person($ctx: Int $id) {
if(my $person = $ctx->model('People')->find(id=>$id)) {
$ctx->stash(person=>$person);
} else {
$ctx->forward('not_found', ["Person of id: $id doesn't exist"])
}
}
multi method person($ctx: Email $email) {
if(my $person = $ctx->model('People')->find(email=>$email)) {
$ctx->stash(person=>$person);
} else {
$ctx->forward('not_found', ["Email: $email doesn't exist"])
}
}
method create($ctx: Dict[name=>Str, email=>Email, birthdate=>DateTime] $post) {
my $validated_post = $ctx->model('ValidatePersonCreatePost')->validate($post);
if($validated_post->is_valid) {
$ctx->model('People')->create($validated_post);
$ctx->forward('CreatedNewPerson', ["I created person with $validated_post"]);
} else {
$ctx->stash(template=>'people/create_has_errors');
}
}
method edit_person($ctx: Int $id, Dict[name=>Str, email=>Email, birthdate=>DateTime] $post) {
if(my $person = $ctx->model('People')->find(id=>$id)) {
my $validated_post = $ctx->model('ValidatePersonEditPost')->validate($post);
if($validated_post->is_valid) {
$ctx->model('People')->update($validated_post);
$ctx->forward('UpdatedPerson', ["I updated person $id with $validated_post"]);
} else {
$ctx->stash(template=>'people/create_has_errors');
}
} else {
$ctx->forward('not_found', ["Person $id doesn't exist"]);
}
}
method delete($ctx: Dict[id=>Int]) {
if(my $person = $ctx->model('People')->find(id=>$id)) {
$person->delete;
$ctx->forward('DeletedPerson', ["I deleted person $id"]);
} else {
$ctx->forward('not_found', ["Person $id doesn't exist"]);
}
}
method prepare_database(HashRef $opts) {
## just a plain old method, move along
}
}
This controller now maps the following URLs:
{hostname}/people/gallery?page={page}&rows={rows}
{hostname}/people/person/{id}
{hostname}/people/person/{email}
{hostname}/people/create (expects POST entity)
{hostname}/people/edit_person/{id} (expects POST entity)
{hostname}/people/delete {expects POST entity}
To make this a bit shorter, I said edits and deletes could only occur using a Person ID, even though I allow view access via both the ID and Email. Now, these are not the most concise and beautiful controllers (there's a bit of repeated code), but they have the advantage of: 1) straightforward for newcomers, and 2) they remain basically plain old Perl / Moose.
At this point, we could offer a refactoring using standard Perl techniques, such as pulling out the repeated code blocks. This would reflect a developers growing confidence and ability. I will leave that as an exercise for the reader, since it's offtopic for the current thought experiment. I only mention it because I want to point out that it would be possible for a developer to gradually increase their ability and get rewarded with better code along a more gradual learning curve.
At this point, now that our developer is starting to become more familiar and confident with the system (given the ease of success so far) she starts exploring the store of community best practices, trying to see what could be done to make these even better and easier. She comes across chaining, and with some research and help managed to perform the following refactoring (I'm using a possible chained syntax which has not yet been fully research, but hopefully you can get the idea:
use Catalyst::Declare::Controller;
class People {
Person->config(actions => {
root => {
name => '';
},
});
method root($ctx:) {
$ctx->stash(people_rs => $ctx->Model('People'));
}
under root($chained_ctx:) {
my $people_rs = $chained_ctx->stash->{people_rs};
method gallery($chained_ctx: Int :$page = 1, Int :$rows = 1) {
$chained_ctx->stash(
people_rs = $people_rs->search({},{page=>$page,rows=>$rows}),
);
}
method create($chained_ctx: Dict[name=>Str, email=>Email, birthdate=>DateTime] $post) {
my $validated_post = $chained_ctx->model('ValidatePersonCreatePost')->validate($post);
if($validated_post->is_valid) {
$people_rs->create($validated_post);
$chained_ctx->forward('CreatedNewPerson', ["I created person with $validated_post"]);
} else {
$chained_ctx->stash(template=>'people/create_has_errors');
}
}
method delete($chained_ctx: Dict[id=>Int]) {
if(my $person = $result_rs->find(id=>$id)) {
$person->delete;
$chained_ctx->forward('DeletedPerson', ["I deleted person $id"]);
} else {
$chained_ctx->forward('not_found', ["Person $id doesn't exist"]);
}
}
multi method person($chained_ctx: Int $id) {
if(my $person = $people_rs->find(id=>$id)) {
$chained_ctx->stash(person=>$person);
} else {
$chained_ctx->forward('not_found', ["Person of id: $id doesn't exist"])
}
}
multi method person($chained_ctx: Email $email) {
if(my $person = $people_rs->find(email=>$email)) {
$ctx->stash(person=>$person);
} else {
$ctx->forward('not_found', ["Email: $email doesn't exist"])
}
}
under person($chained_ctx:) {
my $person = $chained_ctx->stash->{person};
method view ($chained_ctx:) {
## just goes to a view template
}
method edit($chained_ctx: Int Dict[name=>Str, email=>Email, birthdate=>DateTime] $post) {
my $validated_post = $chained_ctx->model('ValidatePersonEditPost')->validate($post);
if($validated_post->is_valid) {
$person->update($validated_post);
$chained_ctx->forward('UpdatedPerson', ["I updated person $id with $validated_post"]);
} else {
$chained_ctx->stash(template=>'people/create_has_errors');
}
}
}
}
method prepare_database(HashRef $opts) {
## just a plain old method, move along
}
}
These map the following URLS:
{hostname}/people/gallery?page={page}&rows={rows}
{hostname}/people/person/{id}/view
{hostname}/people/person/{email}/view
{hostname}/people/person/{id}/edit (expects POST entity)
{hostname}/people/person/{email}/edit (expects POST entity)
{hostname}/people/create (expects POST entity)
{hostname}/people/edit_person/{id}
{hostname}/people/delete {expects POST entity}
You probably noticed I changed a lot of the "$ctx:" invocants to "$chained_ctx:" This is to reflect that the method is part of a chained action context, and different from 'normal' actions. I think it's a good idea to express this difference clearly, since an action that is part of a Chain does have different characteristics than a stand alone action. for example, you can't $ctx->forward to it. I'm not attached to particular invocant name, but I'm just trying to indicate how this could be done without falling back on adding modifiers on the method (such as 'on', 'at' or others).
Overally, this refactor I think is an improvement, since it has less repeated code and the flow across the actions is more clear. It will make it easier to do even more complicated things later.
Well, that's mostly it for the thought experiment. I think I got my basic idea across.
I said earlier in this post that these controller examples have a fatal flaw, which is the amount of logic that rightfully should stay in the model. My suggestion, if you are using DBIC, is that you treat DBIC methods (search, find, and all that) as sort of private or protected to your result and resultset classes. In other word, your schema should define an API to expose to the world, and the world should respect that or suffer. Additionally it's probably suspect to pass a resultset or row object to your Template handler. This can lead to all sorts of template abuse. I promised a last refactor, but as I look into the eager eyes of my Akita dog, who is gazing expectly out the window onto a beautiful sunny day, I just know I'll leave that one to another blog :)
Share and Enjoy!
---UPDATES ---
It was pointed out to me that using the method signature invocant in the way I have described doesn't really reflect the true nature of the invocant. As an alternative we could say that if a method signature has a first, positional parameter which is a Catalyst Context, then the method is an action. Would look like:
method myaction(Context $ctx, ....) {}
This would have the intriguing possibility of having subclasses on the Context subtype, allowing multi method polymorphism for the same URL with different information in the context. This is an alternative to what was described above and may be a better option.
Additionally, with recent builds of MooseX::Declare, methods now support a version of a 'returns' modifier, which is a type constraint on the return value of the method. Although this itself is not always useful for Catalyst actions, it might be valuable to place a modifier to check the contents of the Stash. This would add a bit of sanity to the otherwise anarchy of the Catalyst Stash. Consider:
method person(Context $ctx, Int $id) stashes(Person :$person ) {
$ctx->stash(person=>$ctx->model('People')->find($id));
}
Thoughts welcomed!
The recent release of Perl Catalyst 5.8 has does wonders for our ability to incorporate new developers and new ideas into the Elegant Perl Web Application Framework. As I have reflected on in other posts, Moose based projects offer a shared context that eases the learning curve barrier for newcomers. I look forward to the coming explosion of thoughts and ideas!
One idea that keeps coming up is how to replace the existing method attribute system used by Catalyst Controllers to map Actions to URIs into something more flexible and sustainable. If you don't know what I mean you should read here. Basically, Catalyst Controller classes use these method attributes as a way to 'tag' actions with the information necessary to tie a given controller + action to a URI endpoint. This system, which has endured from the first versions of Catalyst (brought over from Catalyst's inspirational ancestor, Maypole) is starting to show its weaknesses. Although attributes are cute, this is a system unique to Catalyst controllers, so it doesn't make good use of leveraging code and knowledge from other more widely know skills. For more about troubles with the current method attribute syntax see Declare Catalyst Actions.
A possibility to fix this problem is to create a sort of mini language crafted specifically for the job. This would seem to bring Catalyst more in line with how some other popular frameworks are trending, or even exceed them in some ways. As was recently said to me in IRC, “What I want is the maximum power and flexibility.” From my perspective, I'd rather make controllers easier for the most common cases, and create a system that works in a usable manner even if all you know is how to build normal methods. People with a bit more ability and knowledge should be able to influence or 'hint' the system in order to achieve custom effects, but in the best tradition of Perl you should be usefully productive with minimum knowledge. All the dangerous flexibility is placed one level up the skill scale. As I once read regarding Perl, when the developer is still a baby, baby talk should be adequate to achieve what a baby needs. The knowledge needed to do more then can act to better guide them to develop the skill and best practices necessary.
In order to achieve this “Baby Talk” controller, we need a deeper and more fundamental rethinking of the role of Controllers and how they function, and particularly how creating Controllers fit into the development process. Simply adding a sleek mini language is not going to address what I think of as these main problems. So let's, as a thought experiment, consider how we could create controllers with the minimum expected knowledge. Then, as a second step, let's try to talk things further and see if we can create a controller system that is equally useful for web, command line, GUI or other types of interfaces.
First, here's a 'classic' Catalyst Controller:
package MyApp::Controller::People;
use base 'Catalyst::Controller';
## URL = $base/people/gallery?page={page}&rows={rows}
sub gallery :Path('gallery') {
my ($self, $c) = @_;
my ($page, $rows) = @{$c->request->query_parameters}{qw/page rows/};
}
## URL = $base/people/person/{id}
sub person :Path('person') Args(1) {
my ($self, $c, $id) = @_;
}
## URL = $base/people/create + POST entity
sub create :Path('create') {
my ($self, $c) = @_;
my ($name, $age) = @{$c->request->body_parameters}{qw/name age/};
}
## Plain method, not an action
sub prepare_database {
my($self, %args) = @_;
}
Assume the controllers actually do something useful. The idea here is to show the work involved in mapping a URL and getting some common stuff, like body or post parameters and path arguments. This gets the job done, although the developer is left with a lot of validation work. Notice how there's different ways to get the path parameters from the query and post parameters? In this case the developer has to do a lot of work just to get URI information, and we still have something that's very loosely defined, with lots of room for errors.
Okay, so let's see what we can do if we start with something based on MooseX::Declare instead. Yes, I know I am presupposing knowledge of this module, but: 1) Catalyst is Moose based now and we can assume the developer knows 'baby talk' Moose, 2) knowledge of MooseX::Declare is useful for more than just controllers and 3) It looks familiar enough to other programming systems that the learning curve should be low. However, if you don't know MooseX::Declare, you should go take a quick look now.
Here's the Controller. It's not all “Plain old Perl/Moose” yet, but much closer:
use Catalyst::Declare::Controller; ## Doesn't exist yet :)
class People {
action gallery(Int :$page, Int :$rows) {}
multi action person(Int $id) {}
multi action person(Email $email) {}
action create(Dict[name=>Str,age=Int]) {}
method prepare_database() { #just a plain old method, move along}
}
The idea here is that MooseX::Declare, along with MooseX::Method::Signatures, already offers us a pretty powerful system for expressing arguments, and it's highly introspectable. Why make yet another new signature system, one that is only useful for Controllers, and makes a controller feel like something other than a friendly Perl class?
Now, I know the above example need a bit of explaining, and of course there's gaps, as you would expect at this point. So, going though each action:
Gallery: This has a signature that wants (page=>..., rows=>...) as named arguments and additional expects both those to be an Int (see Moose::Utils::TypeConstraints if you need a primer on Moose TypeConstraints). Since the Int is a standard Moose type constraint, you can leverage the entire type constrain tool chain, including MooseX::Types and coercions, which gives you a lot of possibilities as well as greatly assisting you in your first level validation. The URI generator would map the path parts are $base, 'people' and 'gallery' as is already does, but we'd add in that trailing Hash arguments in the signature become mapped to query parameters. So this makes a URL like “$base/people/gallery?page={page}&rows={rows}”. This is the default, and is probably very close to what you are looking for. I think it works intuitively, and has the bonus of helping people see query parameters as something more than a dumping ground.
Person: I tossed in the 'multi' keyword from MooseX::MultiMethods but I think you get the idea. Again, here's a nice bonus to using Controllers that are just Moose classes , we can take advantage of this great module, and whatever else the future brings along. In this case both methods want a single positioned parameter, which the URL mapper converts into a path part.
Create: This one is the most challenging, but my idea here is that if the final argument in a signature is a collection like type, that means it's a POST or PUT listener, and expects an entity body. We need a bit more work here, but I think it's very doable. Imagine how useful this would be for dealing with uploaded entities as well!
Well, how might you perform the hinting I mentioned. Right now I think this could fit into either the config or possibly as a meta description;
class Person {
meta_description(
actions=>{
person->{
## would change url to: $base/people/{id}
uri => '{base}/people/{id}',
},
}
);
## all the rest...
}
Again, this is still a mock up, but the idea I think is workable, and feels clean to me.
Another intriguing possibility is that once you cleanly separate your Web decorated controllers from the core logic being expressed, it easy to imagine how you could use the same inference to build command line applications. For example, the same system above might reflect to a command line tool as:
myapp.pl people.gallery --page=1 –rows=10
My thinking here is that you'd create a base 'MyApp::People' and then depending on what engines you set up in the configuration, it would generate a 'MyApp::Web::Controller::People' or a 'MyApp::App::Controller::People', or even 'MyApp::SOAP::People', and so on.
Final Thoughts.
I'm opposed to creating a new solution for something that already exists in Perl or that exists in a sufficiently large community (such as Moose). We have a big enough learning curve with Moose and related, and I think the more we add in, the worse off we are. My feeling is that controllers should be the minimal decoration on top of plain old Perl necessary to map URIs to the work required. This would make is possible to leverage the entire existing Moose tool chain as well as the collected wisdom of Moose development best practices. Additionally, since Moose has such a rich set of introspection ability, this should make it possible to make a reasonable inference as to how a given controller maps to a URI even without any additional meta information.
Overall, this has been a thought experiment. There's no code behind it yet. My goal here is to see if I can get you to think differently about how your projects are organized and see if we can come up with something that really is ahead of the the current web development architectures. I really believe that although Model – View – Controller is a solid paradigm for web development, our implementations of it have so far been too tyrannical and have gone down paths that cause confusion as well as make it hard for Perl programmers to use the knowledge they already have. A system as described above would avoid those issues, yet increase options and flexibility, while making it easier to just get started.
The following is an edited and updated version of a Moose advocacy bit I wrote on the Google AppEngine for Perl project mailing list quite a while ago. I decided to republish it here since with the recent release of Catalyst 5.8, which uses Moose as a core technological foundation, I felt it would be a good idea to reflect on why I think this is a great thing. There are quite a few tutorials and code overviews for why Moose is a superior object system (links at the end of the article) but I don't see many postings that are simple reflections on Moose and why I, as a developer, am happy it belongs to Perl.
I also think the Google AppEngine for Perl project is not getting nearly enough love and I'm hoping to draw a bit of attention to it as well.
You can read the original here, but that version has an embarrassing number of spelling and grammar errors :)
Without Moose, I'd probably have lost the faith and left Perl for another language. This is not a lightly written statement, since I've been using Perl as a primary server side development language since 1996. When I first started programming I gravitated toward Perl for two reasons: First, it let me do what I needed to do without getting in the way or forcing me to jump through a lot of hoops. Second, it introduced me to what is now broadly thought of as the Open Source community, an idea of how software development and knowledge sharing could be that excited my idealistic nature. I have to imagine there are quite a few programmers in Perl with a similar story. A lot of us started using Perl because the module CGI made it trivial to create server side web forms, while DBI created a straightforward means to shuffle information between the web and back end databases. Perl did what I needed it to do.
However, over the time, my love affair with Perl diminished. Perl's default Object Oriented system is very minimalistic and I found myself envious of languages that allowed --and even encouraged-- a best practices approach in modeling your application. For the first time I started to feel like Perl was getting in the way of me just doing what I needed to do, mostly because I was spending a lot of time jumping through hoops and not expressing my problem domain. Even worse, this lack in Perl caused many large projects to create there own ways to close the gaps left open by existing methodologies. How many different and incompatible component systems have been written for Perl? How many systems for making it easier to do object oriented programming? And each time you go to load a new module from CPAN, you end up also loading a bunch of dependencies for yet another half baked OO framework that is only being used on a handful of projects. This reality makes it much harder for newcomers to usefully contribute code, since each time you have to learn a new underlying framework and all its idiosyncrasies. That's why most important Perl CPAN modules have at most a small handful of contributors. Worse, if fed the common belief that Perl is spaghetti code not suitable for larger applications. Now, I do think having competing ideas is good, and that value is one of the things that makes Perl strong, however for basic, core programming tasks the lack of consistency really hurts us. Where would Perl be if CGI had not been so complete, or if DBI did not become the dominate method of accessing databases?
Moose has reinvigorated my love for the Perl language, it's community and programming in general. It cleanly solves the problem of Perl's minimal OO features. I write less boilerplate and spend more time expressing my problem domain. It provides a wealth of expression around which a rich economy of best practices and shared solutions can grow. Additionally, since it's written for programmers by programmers, I find that it fits my brain better than any other ivy tower OO solution. It has a large and growing community of active developers, more than any other competing system OO framework for Perl AFAIK. Lastly, some of the most important Perl projects of our day, such as Catalyst, DBIx::Class, etc are either using Moose or moving toward Moose. For me, Moose makes OO Perl just as expressive and fun as I found Perl to be when I first started writing website applications all those years ago.
Is it perfect? No, certainly there is a lot of room for Moose to improve. However, all the right decisions to let those improvements happen have been made. The developer community is very serious about feature enhancements and about growing adoption of the framework as well as growing the number of contributors. I am a case in point, since although I've been using Perl for more than 10 years, Moose is one of a small number of projects that I've managed to become involved at the contributor level. Moose not working the way you need it to? That's fine, come to IRC and make your case. However, I would encourage to do your homework first, since the Moose channels have some of the brightest minds I have ever had the chance to see in action.
For me, properly written Perl starts with Moose. It should serve as the basis for any serious project you may be about to undertake.
- For more about Perl.
- For more about Moose.
- The Official Moose Learning Manual
- For more about the Google AppEngine for Perl project.
Here's an article you might have missed. Nice summary of the main features of the 5.8 version of Catalyst.