[Catalyst-commits] r14222 - trunk/examples/CatalystAdvent/root/2011/pen

lukast at dev.catalyst.perl.org lukast at dev.catalyst.perl.org
Fri Dec 16 18:04:26 GMT 2011


Author: lukast
Date: 2011-12-16 18:04:25 +0000 (Fri, 16 Dec 2011)
New Revision: 14222

Added:
   trunk/examples/CatalystAdvent/root/2011/pen/controllerrole_chainaction_massascre_2.pod
Log:
controllerroles part2, please read and verify

Added: trunk/examples/CatalystAdvent/root/2011/pen/controllerrole_chainaction_massascre_2.pod
===================================================================
--- trunk/examples/CatalystAdvent/root/2011/pen/controllerrole_chainaction_massascre_2.pod	                        (rev 0)
+++ trunk/examples/CatalystAdvent/root/2011/pen/controllerrole_chainaction_massascre_2.pod	2011-12-16 18:04:25 UTC (rev 14222)
@@ -0,0 +1,735 @@
+=head1 The ControllerRole ChainAction Massacre (Part 2)
+
+=head2 Introduction
+
+Do you remember L<The ControllerRole ChainAction Massacre (Part 1)|http://www.catalystframework.org/calendar/2011/10>?
+We were innocent. Sitting on the floor, playing Lego and cuddeling with some tiny little actions.
+It was fun! 
+
+But as Roland Deschain of Gilead, the Gunslinger, once stated: "The world has moved on!"
+
+Now it's time for some x-rated adult stuff...
+
+=head2 About this article
+
+This article mostly deals with problems which might occur when reusable actions are
+implemented with Moose::Role. It includes some examples to explain the problems, and some
+hints how to solve them.
+It introduces Moose's BUILD and BUILDARGS methods, and shows an alternative way to
+add roles to Catalyst controllers, including some information about how this 
+can influence your controllers and controllerroles.
+It starts to look behind the scenes and somehow pushes controllerroles to the next level.
+
+The section about performance is postponed. The author himself seems to have some performance issues and could not finish it in time...
+
+=head2 Honey, I need you!
+
+Creating roles with attributes in them is great. Attributes make your code more configurable, 
+flexible and reusable. When you use attributes, you don't have to care about creating getters and
+setters. Moose does that for you. But using attributes in roles
+has a drawback: You can not reliably require them. You can require methods. 
+Attributes have accessors, which are methods. 
+You can require attributes by requiring their accessors.
+
+=head3 But:
+
+Attributes, including their accessors, are created at runtime by calling L<has|Moose/has>. 
+Ok. Why not. Or: Why is this interesting?
+Depending on how you write your roles, the "order" in which they are applied to a class matters.
+
+If you have a role like this:
+
+	package CatalystX::TraitFor::Controller::MyGetModel;
+	
+	use MooseX::MethodAttributes::Role;
+
+	has stash_model_as => ( ... );
+
+	sub get_model :Chained("/") :PathPart("") :CapturArgs(0){ ... }
+
+	use namespace::autoclean;
+	1;
+
+and a second role like this:
+
+	package CatalystX::TraitFor::Controller::MyFoo;
+	
+	use MooseX::MethodAttributes::Role;
+
+	requires qw/get_model stash_model_as/;
+
+	sub foo :Chained("get_model") :PathPart("foo") :CapturArgs(0){
+		my ($self, $c) = @;
+		my $model = $c->stash->{$self->stash_model_as};
+		...
+	}
+
+	use namespace::autoclean;
+	1;
+
+and you want to use both of them in your controller:
+
+	package MyApp::Controller::FooController;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with qw/
+		CatalystX::TraitFor::Controller::MyGetModel
+		CatalystX::TraitFor::Controller::MyFoo
+	/;
+
+	no Moose;
+	1;
+
+Your application will not start.
+Moose will complain about missing the "stash_item_as" method which is required
+by MyFoo, even though it would be present 
+in the object after creation. This is not a bug. Moose complains because it has to. 
+
+Simply speaking: Roles are added by a call to L<Moose/with>.
+All roles are added  with the same call. The contained attributes would satisfy
+all requirements,
+but the requirements are checked before the attributes are created. MyFoo is not satisfied. Moose complains.
+
+The Moose Manual includes a L<small paragraph| http://search.cpan.org/dist/Moose/lib/Moose/Manual/Roles.pod#Required_Attributes>
+about this, including a workaround for attributes defined in the class itself.
+
+=head3 The "consuming-class-approach"
+
+One way to bypass this is to make sure that the roles are added in the correct order. This can be done by manually calling
+"with" several times:
+
+	package MyApp::Controller::FooController;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with q/CatalystX::TraitFor::Controller::MyGetModel/;
+	with q/CatalystX::TraitFor::Controller::MyFoo/;
+
+	no Moose;
+	1;
+
+In this scenario, "MyGetModel" is added first. Moose checks its requirements, and executes the code. All
+attributes and their accessors are added to the consuming class.
+"MyFoo" is added afterwards. It will not complain, because the required methods have already been added to 
+the consuming class. Everything is fine. 
+
+=head3 But++:
+
+=over
+
+=item * Every developer who wants to use your roles has to know about their requirements, and how to fulfill them.
+
+The roles can not be "just plugged" into every controller. 
+The developer has to know some of your roles internals,
+and care about them. A problem which originates in your roles has to be solved
+in the consuming class. 
+
+=item * Circular requirements can not be resolved. 
+
+If MyFoo requires an attribute which is included in MyBar, and MyBar
+requires an attribute which is included in MyFoo, both roles can not be used in
+the same controller without resolving the
+requirements manualy. Yes, you are right. Circular requirements are evil and 
+should be avoided. But it happens from time to time,
+and you never
+know how your roles will be used in the future.
+
+=back
+
+=head3 The "role-itself-approach"
+
+Another approach is not to wait for Moose to create your accessors:
+
+	package CatalystX::TraitFor::Controller::MyGetModel;
+	
+	use MooseX::MethodAttributes::Role;
+
+	# rename the attribute
+	has _stash_model_as => ( 
+		init_arg => "stash_model_as"
+		... 
+	);
+
+	# create you own setter/getter
+	sub stash_model_as{
+		my $self = shift;
+		$self->_stash_model_as(@_);
+	}
+
+	sub get_model :Chained("/") :PathPart("") :CapturArgs(0){ ... }
+
+	use namespace::autoclean;
+	1;
+
+Now, "stash_model_as" is not created at runtime anymore. It is an ordinary 
+subroutine and therefore recognized by every
+role that requires it. The attribute itself did not change much. We can still use typeconstraints,
+coercion, delegation and all that candy. 
+By setting the "init_arg", even the attributes key in the config hash
+stays the same. The role can be consumed by every controller again. 
+The order in which roles are added to the consuming class
+does not matter anymore, and circular requirements are resolved.
+
+The price we pay is obvious: We have to write more code.
+Additionally,
+accessor manipulation inherited from L<Class::MOP|Class::MOP::Attribute/Creation>
+does not work anymore. 
+
+=head2 A stitch in time saves nine
+
+At the moment, we are doing something like this in our roles:
+
+	package CatalystX::TraitFor::Controller::GetRS;
+
+	...
+
+	sub get_resultset :CaptureArgs(1) :PathPart("") { ... }
+	...
+
+And something like this in the consuming controller:
+
+	package MyApp::Controller::Foo;
+
+	...
+
+	__PACKAGE__->config(
+		action => {
+			get_resultset => { PathPart => "foo" },
+		},
+	);
+	...
+
+Now think of Moose as a tailor. Objects are clothes, classes (including their 
+roles) are sewing patterns
+and parameters passed to the constructor are material. 
+Catalyst controllers are suits.
+You can order clothes by creating dot.pm order sheets.
+
+What we are doing above is a little bit like ordering a red suite with knobs, 
+and 
+adding a note to the sheet which says "please use red knobs aswell".
+We have to remind the taylor of doing that, otherwise he would use
+transparent default-knobs.
+
+This is not very clever. Let's create a smarter sewing pattern. 
+The default pattern
+lets the customer choose all material (by setting __PACKAGE__->config).
+We should keep this behaviour, but the default colour should be the same
+as the suite itself. 
+
+Here we go: (yes, GetRS is the knob-role)
+	
+	package CatalystX::TraitFor::Controller::GetRS;
+
+	...
+
+	sub BUILDARGS{
+		my ($class, %params) = @_;
+		# verify that no pathpart has been configured so far
+		unless( %params 
+			&& $params{action}
+			&& $params{action}->{get_resultset}
+			&& $params{action}->{get_resultset}->{PathPart}
+		){
+			# create a new pathpart, based on the classname
+			my @split = split /::/, $class;
+			my $pathpart = $split[-1];
+			$pathpart =~ tr/[A-Z]/[a-z]/;
+			$params{action}->{get_resultset}->{PathPart} = $pathpart;
+
+		}
+		return \%params;
+
+	}
+
+	...
+	1;
+
+The L<BUILDARGS|https://metacpan.org/module/Moose::Manual::Construction#BUILDARGS> 
+method is called as a class method at construction time. 
+It receives all parameters passed to the constructor as is, and 
+it is expected to return a hash reference of named parameters which is 
+used to construct the object.
+The code above inserts a pathpart for the get_resultset method if none
+was specified before.
+
+This example is not completely correct. If you have several roles implementing BUILDARGS, Moose will complain.
+If a BUILDARGS method is implemented in the consuming controller, it will silently override the role implementation. Your role
+will not work as expected. "Overriding" existing BUILDARGS method included in other roles will not work. 
+Methods implemented in
+a role  behave like methods implemented in the consuming class itself, and "override" is limited to methods which come from a superclass.
+This restriction is not limited to BUILDARGS. It is relevant for all role-methods.
+
+Using the "around" modifier works fine:
+
+	around BUILDARGS => sub{
+		my ($orig, $class, %params) = @_;
+		%params = %{ $class->$orig(%params)}; # This calls any other BUILDARGS implementation
+		unless( %params 
+			&& $params{action}
+			&& $params{action}->{get_resultset}
+			&& $params{action}->{get_resultset}->{PathPart}
+		){
+			my @split = split /::/, $class;
+			my $pathpart = $split[-1];
+			$pathpart =~ tr/[A-Z]/[a-z]/;
+			$params{action}->{get_resultset}->{PathPart} = $pathpart;
+
+		}
+		return \%params;
+	};
+
+You should always specify your BUILDARGS method like that, even in the class 
+itself. All other implementations of BUILDARS (up to L<Moose::Object>, and 
+including roles) will be called.  
+As long as your role does not change any parameters except 
+its own, you don't have to worry about conflicts.
+
+All this coding in the role, just to safe a single line of code in the 
+consuming controller...
+
+Yes! If your role is used just 16 times, you are a winner. But that's not the point.
+The point is: Reasonable defaults make using your roles more intuitive.
+Additonaly, the chance of creating bugs by misspelling or forgetting pathpart
+definitions decreases. 
+
+Do I have to mention that BUILDARGS is quite powerfull, and not limited 
+to pathpart-manipulation? I don't think so.
+
+=head2 Under construction
+
+Moose includes another way to manipulate how your objects are created:
+The L<BUILD|https://metacpan.org/module/Moose::Manual::Construction#BUILD> method.
+It is called as object method and receives the parameter hash passed to new, after
+eventually updating it with BUILDARGS.
+
+It is is often use to perform complex verifcation, like this:
+
+	sub BUILD{
+		my $self = shift;
+
+		die "very complex verification failed"
+			unless $self->do_very_complex_verifivation;
+	}
+
+But it can do more. It can even add method modifiers based on construction
+parameters. Let's create a role which ensures that a HTTP "person_id" parameter
+always matches the currently authenticated user, unless the user is a superuser:
+
+	package CatalystX::TraitFor::Controller::OverridePersonId;
+
+	use MooseX::MethodAttributes::Role;
+
+	sub BUILD{
+		my ($self, %params) = @_;
+		my $modified_methods = $params{"override_person_id"} || ["edit"];
+		foreach (@$modified_methods){
+			if($self->can($_)){
+				$self->meta->add_before_method_modifier(
+					$_,
+					sub{
+						my ($self, $c) = @_;
+						unless($c->check_user_roles(["superuser"])){
+							$c->req->params->{person_id} = $c->user->id;
+						}
+					},
+				);
+			}
+			else{
+				die ref($self) . "does not implement $_. Check your configuration";
+				# the first dead body so far. This seems to be a peaceful massacre...
+			}
+		}
+		return $self;
+	}
+
+	use namespace::autoclean;
+	1;
+
+And this is how the role is used:
+
+	PACKAGE MyApp::Controller::WithPersonId;
+
+	use Moose;
+	extends 'Catalyst::Controller';
+	with "CatalystX::TraitFor::Controller::OverridePersonId";
+
+	sub create_something :Local {
+		my ($self, $c) = @_;
+		my $person_id = $c->req->param("person_id");
+		... 
+	}
+
+	__PACKAGE__->config(
+		override_person_id => ["create_something"],
+	);
+
+	no Moose;
+	1;
+
+It is assumed that L<Catalyst::Plugin::Authentication> and L<Catalyst::Plugin::Authorization::Roles> are in use.
+Class::MOP's
+L<add_before_method_modifier|Class::MOP::Class/Method-Modifiers> is used to add the method modifiers.
+If you need access to the roles configuration within your controller, you can add an attribute for it.
+To make this role more reusable, the name of the overwritten parameter, and the superuser-role should
+be configurable aswell...
+
+Specifying a BUILD method within a role results in the same problems as specifying a BUILDARGS method.
+Unfortunately, the BUILD method is special. It is kind of bitchy, a real drama queen. 
+Moose relies on  BUILD methods of superclasses beeing executed in the right order. The BUILD method of a 
+superclass always has to be called before the extending classes BUILD method, otherwise it is not guaranteed
+that the base object is complete and valid.
+According to the L<Moose Manual|https://metacpan.org/module/Moose::Manual::Construction#BUILD-and-parent-classes>, 
+applying method modifiers to BUILD is not supported.
+
+=head3 The "do-it-anyway-approach"
+
+Nevertheless, the L<Moose Cookbook| Moose::Cookbook::Extending::Recipe2> suggests to
+use after-modifiers for BUILD, like this:
+
+	sub BUILD{}
+
+	after BUILD => sub{
+		...
+	};
+
+The empty BUILD stub ensures that a BUILD method is present. If it wouldn't, it wouldn't be
+possible to add a method modifier for it. If the consuming class has its own BUILD, the empty
+stub will silently be overwritten.
+
+This solution often works fine. But remember: It is not officially supported. I had situations
+where it did its job, but i also remember situations where it just didn't work out. I was not
+able to find out why, but after removing all method modifiers applied to BUILD, everything worked
+as expected. (Since I still do not know the exact reason, it is possible that it was a bug in my code...)
+
+=head3 The "do-it-yourself-approach"
+
+It is possible to solve this problem by yourself, within the consuming controller.
+Moose lets us exclude and alias role methods. If our controller does not implement 
+its own BUILD method, and only one role includes a custom BUILD, we do't have to 
+do anything. It will just work. If several BUILD methods are in charge, 
+we can rename them and call them manualy inside the classes BUILD:
+
+	package MyApp::Controller::WithBuild;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with "CatalystX::TraitFor::Controller::FirstBuildRole" => { 
+		-excludes => qw/BUILD/, -alias => { BUILD => "_first_roles_BUILD",} },
+	     "CatalystX::TraitFor::Controller::SecondBuildRole" => { 
+		-excludes => qw/BUILD/, -alias => { BUILD => "_second_roles_BUILD",} };
+
+	sub BUILD{
+		my ($self, %params) = @_;
+
+		$self = $self->_first_roles_BUILD(%params);
+		$self = $self->_second_roles_BUILD(%params);
+
+		# your controllers BUILD code here
+
+		return $self;
+	}
+
+	no Moose;
+	1;
+
+BUILD is not modified anymore. The controllers BUILD is an ordinary method and
+can be handled by Moose. The roles BUILD methods are renamed (in fact, they are 
+excluded and re-added with a different name). They do not conflict with 
+any other BUILD implementation anymore, and can therefor be called by BUILD without 
+creating conflicts.
+
+This approach again has the problem that the developer has to know your roles
+internals, and that a role-problem has to be solved by hand when the role is used.
+The good thing about this approach is that it will always work, as long as
+the developer remembers to implement it.
+
+The first approach is much more elegant, but not officially supported.
+Using it can have unforseen consequences.
+
+No matter which way you prefere, you should alwas add a comment to your
+roles documentation if you use a BUILD method. This makes it possible
+to find out why things are going wrong if thei are going wrong.
+If your documentation does not include such a hint, a developer using
+your role will most likely not be able to figure out the problem
+in case of conflicts.
+
+
+=head2 Trait me well
+
+The CPAN is huge. It contains so many modules. 
+And most of them are realy usefull!
+L<CatalystX::Component::Traits> for example. It is a Moose::Role for Catalyst 
+Components, which adds support for traits. 
+Traits are roles which are applied to an instance, and not to
+a class. This does matter! But we will come to that later. First, an example:
+
+If your controller has L<CatalystX::Component::Traits> applied to it:
+
+	package MyApp::Controller::Foo;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with "CatalystX::Component::Traits";
+
+	no Moose;
+	1;
+
+You can enable your ControllerRoles within your applications configuration:
+
+	package MyApp;
+
+	use Catalyst qw/ConfigLoader/;
+
+	...
+
+	__PACKAGE__->config(
+		"Controller::Foo" => {
+			traits => [
+				"+CatalystX::TraitFor::Controller::ModelActions",
+				"+CatalystX::TraitFor::Controller::MyFo",
+				],
+		}
+	);
+
+	...
+
+This is (almost) equivalent to specifying the roles in your class:
+
+	package MyApp::Controller::Foo;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with qw/
+		CatalystX::TraitFor::Controller::ModelActions
+		CatalystX::TraitFor::Controller::Foo
+		/;
+
+	no Moose;
+	1;
+
+I prefere the "classic" approach, without CatalystX::Component::Traits. 
+It feels right to 
+specify all the functionality and actions within the controller class, and use the
+config hash for configuration only. This also makes it easy to modify my controller
+by creating method-modifiers for methods supplied by my roles. I will edit my controller anyway. 
+Adding a few "with"-lines does not hurt me. But this is 
+just my personal opinion. There is more than one way to do it! I am NOT interested
+in a flame-war! L<CatalystX::Component::Traits> is great. I have used it in the past, and 
+I will use it i the future. It has 
+usefull features, like "trait searching" and "trait merging". And, of course:
+There are these little differences...
+
+The fact that traits are applied to objects instead of classes matters in 
+several ways, including the following:
+
+=head3 It influences inheritance
+
+If you specify your controller like this:
+
+	package MyApp::Controller::Foo;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with qw/
+		CatalystX::TraitFor::Controller::ModelActions
+		CatalystX::TraitFor::Controller::Foo
+		/;
+
+	no Moose;
+	1;
+
+and you have a second controller which extends this foo-controller:
+
+	package MyApp::Controller::Bar;
+
+	use Moose;
+	extends "MyApp::Controller::Foo";
+
+	no Moose;
+	1;
+
+Bar will inherit everything from Foo, including all methods, actions and 
+attributes specified in any of the consumed roles. This is not necessarily
+true when traits are in use. If you specify your traits within the applications
+config (as shown above), they are not part of the controller. Bar will
+inherit everything from Foo, but not the traits, and nothing specified in any of the traits.
+
+It is also possible to specify the traits within the controllers config:
+
+	package MyApp::Controller::Foo;
+
+	use Moose;
+	extends "Catalyst::Controller";
+	with qw/
+		CatalystX::Component::Traits
+		/;
+
+	__PACKAGE__->config(
+		traits => [qw/ 
+			+CatalystX::TraitFor::Controller::ModelActions
+			+CatalystX::TraitFor::Controller::MyFoo
+			/ 
+		],
+	);
+
+	no Moose;
+	1;
+
+In that case, Bar will inherit everything again.
+
+=head3 It influences your code
+
+Using traits instead of roles also influences you when you are writing
+reusable code with Moose::Role.
+
+Do you remember the dynamically created "stash_model_as" attributes from L<The ControllerRole ChainAction Massacre (Part 1)|http://www.catalystframework.org/calendar/2011/10>
+? The controllers
+classname was used to dynamically create the key which is used to store the model in the stash. 
+The key was created like this:
+
+	sub{
+		my @split = split "::", ref(shift);
+		my $controllername = pop @split;
+		$controllername =~ tr/[A-Z]/[a-z]/;
+		return $controllername . "_model";
+	}
+
+CatalystX::Component::Traits uses L<MooseX::Traits::Pluggable> to create
+a new anonymious class with all traits applied to it. This results in a classname
+like this:
+
+ Moose::Meta::Class::__ANON__::SERIAL::33
+
+With our "old" code, the resulting key for my foo-controller would be
+
+ 33_model
+
+and not
+
+ foo_model
+
+This might work if you always use the "stash_model_as" attribute, but it is 
+ugly and risky. 
+And think of our BUILDARGS method. It will result in the pathpart
+beeing "33" insted of "foo", which is realy bad.
+
+We can use some Moose internals to bypass this. 
+Since we all like reuable code,
+lets create a subroutine which extracts the "Foo" from 
+"MyApp::Controller::Foo", no matter if it is anonymous or not:
+
+	sub _get_base_name{
+		my $self = shift;
+		my $classname;
+		# handle anonymous class
+		if($self->meta->is_anon_class){
+			# fetching superclasses
+			my @superclasses = map { Class::MOP::class_of $_} $self->meta->linearized_isa;
+			foreach(@superclasses){
+				# searching the first non-anonymous class
+				unless($_->is_anon_class){
+					# store its name
+					$classname = $_->name;
+					last;
+				}
+			}
+		}
+		else{
+			# store the classname if called on an object
+			$classname = ref($self) || $self;
+		}
+		# return the last part of the name
+		pop [split /::/, $classname];
+	}
+
+Or, shorter:
+
+	use List:Util qw/first/;
+	sub _get_base_name{
+		my $self = shift;
+		pop [split /::/, $self->meta->is_anon_class ? (
+			first { ! $_->is_anon_class } map {Class::MOP::class_of $_ } $self->meta->linearized_isa
+		)->name : ref($self) || $self];
+	}
+
+This method will return the last part of the first non-anonymous classname
+we are inheriting from. It will work with classes and objects.
+
+Now we can create our default stash-key for the model like this:
+
+	sub{ shift->_get_base_name =~ tr/[A-Z]/[a-z]/r . "_model" }
+
+Since our helper can be called as a class- and as a object-method, we can 
+use it in our BUILDARGS method aswell.
+
+Notes:
+
+=over
+
+=item * Class::MOP::class_of is used to find the metaclass for a given classname
+
+=item * $metaclass->is_anon_class is used to find out which classes are anonymous
+
+=item * $metaclass->linearized_isa is used to get a list of all parent classes
+
+=item * $metaclass->name returns the classname
+
+=back
+
+Thanks to the people in #moose for usefull tips.
+
+=head3 The main difference
+
+is, obviously, that not all objects of a class do the same roles anymore.
+It is possible to create two objects of the same class which have different 
+attributes and object methods.
+This is not supported by CatalystX::Component::Traits, and
+this is most likely not what you want for your 
+controller. All controllers of the same class are expected to handle 
+requests equally. 
+But you might want that in the model. Possibly if you want the model to 
+behave different if the current request is done by an authenticated user.
+
+=head2 Conclusion
+
+=over
+
+=item * Roles do not offer the same functionality as classes.
+
+=item * Most restrictions can easily be bypassed without evil hacking.
+
+=item * Carefully designing a role, and providing reasonable
+defaults can safe a lot of time when using it.
+
+=item * Moose offers two different ways to influence your controllers
+build process.
+
+=item * BUILDARGS allows you to modify parameters before the object is created
+
+=item * BUILD allows you to modify the object after creation, but before it
+is returned to the calling context.
+
+It is the princess in the Moose empire. The princess is beautiful, mighty but special. If you treat her right, she will be thankful
+and give you unforseen power. But if she gets mad, she will throw her golden ball into
+the sewers and blame you, the innocent frog.
+
+=item * There is more than one way to do it!
+
+=item * If your role could be used as a trait, it might influence your code.
+
+=back
+
+=head2 Author
+
+Lukas Thiemeier <lukast at cpan.org>
+
+=head2 Post Scriptum
+
+Thank you for reading my article. I hope you had at least as much fun reading it as I had  writing it for you.
+
+Hints, criticism, bugfixes and everything which helps me making my articles better are very welcome!
+Just send a message to lukast at cpan.org.
+
+=head2 I wish you a merry cristmas. Yes, I do!




More information about the Catalyst-commits mailing list