[Catalyst-commits] r9396 - in Catalyst-Controller-RHTMLO/trunk: . lib/Catalyst/Controller lib/Catalyst/Controller/RHTMLO t t/lib t/lib/TestApp t/lib/TestApp/C t/lib/TestApp/Form

jgottshall at dev.catalyst.perl.org jgottshall at dev.catalyst.perl.org
Tue Feb 24 22:11:53 GMT 2009


Author: jgottshall
Date: 2009-02-24 22:11:53 +0000 (Tue, 24 Feb 2009)
New Revision: 9396

Added:
   Catalyst-Controller-RHTMLO/trunk/t/
   Catalyst-Controller-RHTMLO/trunk/t/lib/
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp.pm
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Forms.pm
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Root.pm
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/One.pm
   Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/Two.pm
   Catalyst-Controller-RHTMLO/trunk/t/unit.t
Modified:
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm
   Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm
Log:
First attempts at tests


Modified: Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm	2009-02-24 19:46:15 UTC (rev 9395)
+++ Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO/Action.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -7,7 +7,7 @@
 use MRO::Compat;
 use Catalyst::Utils;
 
-__PACKAGE__->mk_accessors(qw/_config/);
+__PACKAGE__->mk_accessors(qw/stash_name stash_hash form_classes/);
 
 sub execute {
     my $self = shift;
@@ -20,20 +20,19 @@
 
 sub _setup_forms {
     my ( $self, $c ) = @_;
-    my $cfg = $self->_config;
 
-    while( my ($name, $class) = each %{$cfg->{form_classes}} ) {
+    while ( my ( $name, $class ) = each %{ $self->form_classes } ) {
         # setup form
         $c->log->debug("Loading form '$class' as '$name'");
-        my $form = $class->new();
+        my $form = $class->new( app => $c );
         $form->params( $c->req->params );
         $form->init_fields;
 
         # put form in stash (first or only)
-        $c->stash->{ $cfg->{stash_name} } ||= $form;
+        $c->stash->{ $self->stash_name } ||= $form;
 
         # put form in stash under its name, in case of multiple forms
-        $c->stash->{ $cfg->{stash_hash_name} }->{$name} = $form;
+        $c->stash->{ $self->stash_hash }->{$name} = $form;
     }
 
     return;

Modified: Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm	2009-02-24 19:46:15 UTC (rev 9395)
+++ Catalyst-Controller-RHTMLO/trunk/lib/Catalyst/Controller/RHTMLO.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -9,14 +9,15 @@
 
 =head1 NAME
 
-Catalyst::Controller::RHTMLO - Catalyst Base Controller for Rose::HTML::Objects forms
+Catalyst::Controller::RHTMLO - Catalyst base controller for integrating 
+Rose::HTML::Form objects
 
 =head1 SYNOPSIS
 
     package MyApp::Controller::Books;
     use base 'Catalyst::Controller::RHTMLO';
 
-    # loads MyApp::Form::Book (which isa Rose::HTML::Form)
+    # loads MyApp::Form::Book, which should ->isa('Rose::HTML::Form')
     sub edit : Local Form('Book') {
         my ( $self, $c ) = @_;
 
@@ -33,8 +34,8 @@
         }
     }
 
-    # display two search forms on same page
-    sub search : Local Form('ByAuthor,ByTitle') {
+    # display several search forms on same page
+    sub search : Local Form('ByAuthor') Form('ByTitle') {
         my ( $self, $c ) = @_;
 
         if ( $c->stash->{forms}->{ByAuthor}->was_submitted ) {
@@ -47,18 +48,19 @@
 
 =head1 DESCRIPTION
 
-This base controller glues Catalyst actions to form classes derived from 
-L<Rose::HTML::Form>, a component of John Siracusa's excellent L<Rose::Object> 
+This base controller glues L<Catalyst> actions to form classes derived from 
+L<Rose::HTML::Form>, a component of John Siracusa's burgeoning L<Rose> 
 framework. Unlike some other form-loading modules (see L</"PRIOR ART">), this 
 one does not include any mechanism for defining form structures; it merely 
 loads, instantiates, and initializes pre-written form classes for use in your 
 controllers. 
 
-In order to utilize a particular form in a particular Catalyst action, simply declare an attribute on the subroutine:
+In order to utilize a particular form in a particular Catalyst action, simply 
+declare an attribute on the subroutine:
 
     sub edit : Local Form('Book') { }
 
-This will ensure that MyApp::Form::Book is loaded and initialized, basically 
+This will ensure that C<MyApp::Form::Book> is loaded and initialized, basically 
 equivalent to the following:
 
     my $form = MyApp::Form::Book->new();
@@ -72,10 +74,10 @@
 
     sub edit : Local Form('+My::FormClasses::Book') { }
 
-To display more than one distinct form on a page, just list them all in the 
-attribute, delimited with commas or spaces: 
+To utilize more than one distinct form class in the same action, simply declare 
+additional attributes:
 
-    sub search : Local Form('ByAuthor,ByTitle,BySubject') { 
+    sub search : Local Form('ByAuthor') Form('ByTitle') Form('BySubject') { 
         my ($self, $c) = @_;
 
         $c->stash->{forms}{ByAuthor}->action($c->uri_for('/search/byauthor'));
@@ -83,46 +85,39 @@
         $c->stash->{forms}{BySubject}->name('bytopic');
     }
 
-(Note that you must put all the form names inside one set of quotes; I<DO NOT> 
-try to quote each individual form name. This is a limitation of perl5's 
-subroutine attributes.) The first form listed will still be stored in the stash 
-in the usual location; I<all> the forms (including the first) will be stored 
-under a separate stash key, in a hash keyed to the name used to load them.
+The first form listed will be stored in the stash in the usual location; I<all> 
+the forms (including the first) will be stored under a separate 
+(L<configurable|/CONFIGURATION>) stash key, in a hash keyed to the name used to 
+load them.
 
-If for some reason you need to render the same form more than once on a page, 
-just list it again:
-    
-    sub search : Local Form('Search,Search') {
-        my ($self, $c) = @_;
-
-        $c->stash->{forms}{Search}[0]->name('search_0');
-        $c->stash->{forms}{Search}[1]->name('search_1');
-    }
-
-In this (weird but possible) case, the forms will be put into an arrayref in 
-the expected location. I haven't actually attempted to use this technique in 
-production, so I don't know what else you might have to do to make it work...
-
 =head1 CONFIGURATION
 
 You can override many defaults using Catalyst's configuration mechanism:
 
     __PACKAGE__->config(
-        'Controller::RHTMLO' => {
-            form_attr       => 'HasForm',
-            action_class    => 'MyApp::Action::RoseForm',
-            stash_name      => 'formobj',
-            stash_hash_name => 'allforms',
-            form_prefix     => 'MyApp::RoseForm',
-        }
+        # settings for all controllers using this base
+        'Catalyst::Controller::RHTMLO' => {
+            form_attr         => 'HasForm',
+            form_action_class => 'MyApp::Action::RoseForm',
+            form_stash_name   => 'formobj',
+            form_stash_hash   => 'allforms',
+            form_prefix       => 'MyApp::RoseForm',
+        },
+        # settings for specific controllers in MyApp
+        'Controller::Foo' => { # or 'C::Foo' if MyApp is built that way
+            form_prefix       => 'MyApp::FooForms',
+        },
     );
 
 =over
 
-=item C<action_class> (default 'Catalyst::Controller::RHTMLO::Action')
+=item C<form_action_class>
 
-If you want to add more functionality to the automatic form loading and initialization, you can create your own action class:
+Default: C<'Catalyst::Controller::RHTMLO::Action'> 
 
+If you want to add more functionality to the automatic form loading and 
+initialization, you can create your own custom action class:
+
     package MyApp::Action::RoseForm;
     use base 'Catalyst::Controller::RHTMLO::Action';
 
@@ -145,24 +140,31 @@
 
 =item C<form_attr>
 
-Default: 'Form'. Set this to alter the subroutine attribute used to indicate 
-one or more forms to be loaded by a given action, e.g.:
+Default: C<'Form'>
+
+Set this to alter the subroutine attribute used to indicate one or more forms 
+to be loaded by a given action, e.g.:
  
     sub edit : Local HasForm('Books') { }
 
 =item C<form_prefix>
 
-Default: 'MyApp::Form' (using your app's actual name). Set this to the 
-namespace where your Rose::HTML::Form subclasses live.
+Default: C<'MyApp::Form'> (using your app's actual name)
 
-=item C<stash_hash_name>
+Set this to the namespace where your Rose::HTML::Form subclasses live.
 
-Default: 'forms'. Sets the stash key under which all forms for a given action 
-will be stored by class name.
+=item C<stash_hash>
 
+Default: C<'forms'>
+
+Sets the stash key under which all forms for a given action will be stored, 
+keyed according to the name used to load them.
+
 =item C<stash_name>
 
-Default: 'form'. Sets the stash key under which the first form for a given 
+Default: C<'form'>
+
+Sets the stash key under which the first (and often I<only>) form for a given 
 action will be stored.
 
 =head1 PRIOR ART
@@ -175,74 +177,104 @@
 =item L<Catalyst::Controller::FormBuilder>
 
 Provided a lot of insight into how to trigger the form loading process with a 
-custom subroutine attribute. Based on L<CGI::FormBuilder> rather than 
-Rose::HTML::Form. 
+custom subroutine attribute and custom action class. Based on 
+L<CGI::FormBuilder> (rather than L<Rose::HTML::Form>). 
 
 =item L<CatalystX::RoseIntegrator>
 
-Looks like it uses a CGI::FormBuilder-style config file to construct 
-Rose::HTML::Form objects on the fly, rather than having static subclasses. Also 
-seems to include direct model integration with L<Rose::DB::Object>.
+Looks like it uses a L<CGI::FormBuilder>-style config file to construct 
+L<Rose::HTML::Form> objects on the fly, rather than having static subclasses. 
+Also seems to include direct model integration with L<Rose::DB::Object>.
 
 =item L<CatalystX::CRUD::Controller::RHTMLO>
 
-A component that enables use of Rose::HTML::Form objects with Peter Karman's 
+A component that enables use of L<Rose::HTML::Form> objects with Peter Karman's 
 cool L<CatalystX::CRUD> API.
 
 =back
 
 =head1 SEE ALSO
 
-L<Rose::HTML::Form>, L<Rose::HTML::Objects>, L<Rose::Object>, 
+L<Rose::HTML::Form>, L<Rose::HTML::Objects>, L<Rose>, 
 L<Catalyst::Controller>, L<Catalyst::Action>, L<Catalyst>
 
 =head1 AUTHOR
 
 Jason Gottshall <jgottshall att capwiz dott com>
 
+=head1 ACKNOWLEDGEMENTS
+
+mst: for patiently helping me make sense of Catalyst's configuration system
+phaylon: for pointing me to existing documentation on extending components
+
 =head1 LICENSE
 
-This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.
+This program is free software; you can redistribute it and/or modify it under 
+the same terms as Perl itself.
 
 =cut
 
+__PACKAGE__->mk_accessors(
+    qw/
+        form_attr
+        form_action_class
+        form_stash_name
+        form_stash_hash
+        form_prefix
+        /
+);
+
 __PACKAGE__->config(
-    'Controller::RHTMLO' => {
-        form_attr       => 'Form',
-        action_class    => 'Catalyst::Controller::RHTMLO::Action',
-        stash_name      => 'form',
-        stash_hash_name => 'forms',
-    }
+    form_attr         => 'Form',
+    form_action_class => 'Catalyst::Controller::RHTMLO::Action',
+    form_stash_name   => 'form',
+    form_stash_hash   => 'forms',
 );
 
+sub COMPONENT {
+    my ( $self, $c, $arguments ) = @_;
+
+    # merge global config with controller-specific config
+    #TODO refactor this into Moose role for Catalyst components in general
+    $arguments = $self->merge_config_hashes( 
+        $c->config->{'Catalyst::Controller::RHTMLO'},
+        $arguments 
+    ) if defined $c->config->{'Catalyst::Controller::RHTMLO'};
+
+    # set default form prefix according to appname
+    $self->config->{form_prefix} = $c->config->{name} . "::Form";
+
+    return $self->next::method( $c, $arguments );
+}
+
 sub create_action {
     my ( $self, %args ) = @_;
 
-    my $cfg = $self->config->{'Controller::RHTMLO'};
-    $cfg->{form_prefix} ||= $self->config->{'name'} . '::Form';
-
-    if ( my $formnames = delete $args{attributes}{$cfg->{form_attr}} ) {
+    if ( my $formnames = delete $args{attributes}{ $self->form_attr } ) {
         # ensure at least one form name is provided
-        @$formnames = grep { $_ } @$formnames;
+        @$formnames = grep {$_} @$formnames;
         unless (@$formnames) {
-            die "Attribute '$cfg->{form_attr}' for action '$args->{reverse}' missing form name";
+            die sprintf
+                q{Attribute '%s()' for action '/%s' must specify an RHTMLO-based form class},
+                $self->form_attr, $args{reverse};
         }
 
         # validate and load form classes
         foreach my $name (@$formnames) {
             my $class =
-                ( $name =~ s/^\+// )
-                ? join( '::', $cfg->{form_prefix}, $name )
-                : $name;
+                  $name =~ s/^\+//
+                ? $name # leading plus indicates fully-qualified package name
+                : join( '::', $self->form_prefix, $name );
             Catalyst::Utils::ensure_class_loaded($class);
-            $cfg->{form_classes}{$name} = $class;
+            $args{form_classes}{$name} = $class;
         }
 
         # pass config to action
-        $args{_config} = $cfg;
+        $args{stash_name} = $self->form_stash_name;
+        $args{stash_hash} = $self->form_stash_hash;
 
         # set action class
-        push @{ $args{attributes}{ActionClass} }, $cfg->{action_class};
+        push @{ $args{attributes}{ActionClass} }, $self->form_action_class;
     }
 
     return $self->next::method(%args);

Added: Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Forms.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Forms.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Forms.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -0,0 +1,16 @@
+package TestApp::C::Forms;
+
+use strict;
+use warnings;
+
+use base 'Catalyst::Controller::RHTMLO';
+
+sub one_form : Global Form('One') { }
+
+sub two_forms : Global Form('One') Form('Two') { }
+
+sub fully_qualified : Global Form('+TestApp::Other::Form') { }
+
+1;
+
+

Added: Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Root.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Root.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/C/Root.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -0,0 +1,25 @@
+package TestApp::C::Root;
+
+use strict;
+use warnings;
+use base 'Catalyst::Controller';
+use Storable qw/freeze/;
+
+__PACKAGE__->config->{namespace} = '';
+
+sub end : Private {
+    my ( $self, $c ) = @_;
+
+    my $s = $c->stash;
+
+    my ($form, $forms) = $s->{expect} ? @{delete $s->{expect}} : qw/form forms/;
+    foreach my $f ( values %{$s->{$forms}} ) {
+        # app is $c, which doesn't freeze well
+        $f->app({});
+    }
+
+    $c->response->body( freeze( $s ) );
+}
+
+1;
+

Added: Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/One.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/One.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/One.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -0,0 +1,7 @@
+package TestApp::Form::One;
+
+use base 'Rose::HTML::Form';
+
+1;
+
+

Added: Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/Two.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/Two.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp/Form/Two.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -0,0 +1,7 @@
+package TestApp::Form::Two;
+
+use base 'Rose::HTML::Form';
+
+1;
+
+

Added: Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp.pm
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp.pm	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/t/lib/TestApp.pm	2009-02-24 22:11:53 UTC (rev 9396)
@@ -0,0 +1,14 @@
+package TestApp;
+
+use strict;
+use warnings;
+
+use Catalyst::Runtime;
+use Catalyst;
+
+our $VERSION = '0.01';
+__PACKAGE__->config( name => 'TestApp' );
+__PACKAGE__->setup;
+
+1;
+

Added: Catalyst-Controller-RHTMLO/trunk/t/unit.t
===================================================================
--- Catalyst-Controller-RHTMLO/trunk/t/unit.t	                        (rev 0)
+++ Catalyst-Controller-RHTMLO/trunk/t/unit.t	2009-02-24 22:11:53 UTC (rev 9396)
@@ -0,0 +1,20 @@
+use strict;
+use warnings;
+
+use Test::More tests => 3;                      # last test to print
+use Storable qw/thaw/;
+
+# setup library path
+use FindBin qw($Bin);
+use lib "$Bin/lib";
+use Catalyst::Test 'TestApp';
+
+# 1
+use_ok "Catalyst::Controller::RHTMLO";
+
+# 2
+ok( request('/one_form')->is_success, 'can request one form' );
+my $one_form = thaw( get( '/one_form' ) );
+# 3
+isa_ok( $one_form->{form}, 'Rose::HTML::Form', 'form' );
+




More information about the Catalyst-commits mailing list