Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation
 
PerlMonks  

CGI::Application with 'main' runmodes and 'sub' runmodes

by wfsp (Abbot)
on Apr 28, 2007 at 12:08 UTC ( [id://612534]=perlquestion: print w/replies, xml ) Need Help??

wfsp has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on a CGI::App that is growing like topsy. The list of runmodes has past a dozen and is veering crazily toward two.

I remembered dragonchilds comment "every major subsystem... (would) have their own C::A". He posted some code to show how that might be done

I hit some snags implementing it, here is what I finaly came up with.

Main.pm

package App::Main; use strict; use warnings; use base qw{CGI::Application}; use App::One; use App::Two; sub setup { my ($self) = @_; $self->start_mode('one'); $self->mode_param(path_info => 1); $self->run_modes([qw{ one two }] ); } sub one { my ($self) = @_; my $one = App::One->new; $one->run; exit; #$self->header_type('none'); } sub two { my ($self) = @_; my $two = App::Two->new; $two->run; exit; #$self->header_type('none'); } 1;
One.pm
package App::One; use strict; use warnings; use base qw{CGI::Application}; sub cgiapp_init { my $self = shift; $self->tmpl_path(q{tmpl}); } sub setup { my ($self) = @_; $self->mode_param(path_info => 2); $self->start_mode('one_a'); $self->run_modes([qw( one_a one_b )]); } sub one_a{ my ($self) = @_; my $tmpl = $self->load_tmpl; return $tmpl->output; } sub one_b{ my ($self) = @_; my $tmpl = $self->load_tmpl; return $tmpl->output; } 1;
Two.pm would be along similar lines. The instance script would be something like
#!C:/Perl/bin/perl.exe use strict; use warnings; use lib qw{ /www/local/sw/admin/lib }; use App::Main; my $top = App::Main->new; $top->run;
The Main.pm runmodes create new C::A objects. I've used the $self->mode_param(path_info => n); (where n tells C::A which part of path_info to use) to establish which runmode to use. So

index.cgi/one/one_a
will get us to 'main' runmode 'one', 'sub runmode' 'one_a'.

I'll now be able to develope/test each 'main runmode' separately and things should go more smoothly (I said 'should'!).

One point though, in Main.pm I've had to include exit; at the end of each method. Because of the way C::A works the header will have already been added/sent. Just having a return would mean another header would be added. Interestingly, $self->header('none') did what it says on the tin but tacked on the word 'none' to the end of the ouput (after, in this case, the closing html tag).

What do the monks think about the general approach? Worth pursuing or are there pitfalls and traps ahead?

Replies are listed 'Best First'.
Re: CGI::Application with 'main' runmodes and 'sub' runmodes
by derby (Abbot) on Apr 28, 2007 at 12:43 UTC

    Hmmm ... that exit at the end of each method is a real bad code smell. It may be perfectly okay but I think it would give most CGI::Application'ers pause.

    Without knowing about your app, it's really hard to comment on your approach. For me, once the run modes start getting past a half-dozen or so, I start looking for ways to refactor. Either as other apps or by taking a hard look at my runmodes and deciding their not really runmodes but maybe differing views or differing model of a runmode -- moving those decisions further down into the controller.

    -derby
      Thanks for your response.
      ...a real bad code smell.
      I agree. A previous 'refactoring' had each 'main runmode' take the second element of path_info and run it through a dispatch table. I just had the feeling that I could let C::A handle that too.

      My last effort was, as you say, to separate out the main runmodes as separate C::A apps each with its own instance script. I have a Base.pm that inits everything thats needed and all common code is in its own module. So perhaps the separate apps idea is the way to go.

Re: CGI::Application with 'main' runmodes and 'sub' runmodes
by Arunbear (Prior) on Apr 28, 2007 at 15:46 UTC
    CGI::Application::Dispatch was created to help you with problems like this so e.g. instead of your App::Main you'd have an invocation script like:
    #!/usr/bin/perl use strict; use CGI::Application::Dispatch; CGI::Application::Dispatch->dispatch( prefix => 'App', default => 'one', );
    This should do what you want (including the path info dispatch), but CGI::Application::Dispatch has lots more configuration options should you need more power.

    Having said this, I now prefer CGI::Prototype over CGI::App, beacuse with CGI::Prototype, run modes are handled by classes so each of your two dozen (and growing) run modes would live in its own small module. I find this makes the application a lot more manageable as it increases in size.

      I second the vote for CGI::Prototype. I'd also suggest looking at CGI::Ex::App. It lets you choose if you want some run modes handled by other classes, or handled by locally.

      #!/usr/bin/perl package Foo; use strict; use warnings; use base qw(CGI::Ex::App); Foo->new->navigate; exit; sub base_dir_abs { '/var/www/templates' } ### tell the application to ### look for a module to handle each run mode (step) sub allow_morph { 1 } { # This could be in its own file - or # embedded in here is fine # you can get here by going to # /cgi-bin/my_cgi # or /cgi-bin/my_cgi/main # or /cgi-bin/my_cgi?step=main package Foo::Main; sub file_print { return \ <<'HERE'; <h1>Step Main</h1> This is the default run mode. You said [% hello %]. HERE sub hash_swap { return {hello => "I'm main!"}; } } # end of Foo::Main # you get to step2 by calling # /cgi-bin/my_cgi?step=step2 # or # /cgi-bin/my_cgi/step2 # these methods are just sitting the the Foo class sub step2_file_print { return \ <<'HERE'; <h1>I'm in step 2</h1> You said [% hello %]. HERE sub step2_hash_swap { return {hello => 'Hello Step2'}; } # uncomment for a trace of which methods were looked for # sub post_navigate { # use CGI::Ex::Dump qw(debug); # debug shift->dump_history; #}


      my @a=qw(random brilliant braindead); print $a[rand(@a)];
Re: CGI::Application with 'main' runmodes and 'sub' runmodes
by Herkum (Parson) on Apr 28, 2007 at 14:25 UTC

    Suggestions,

    • If you use CGI::Application::Plugin::AutoRunmode you can get rid of the setup method.
    • I would keep the name of the instance scripts closely aligned with the Controller names.
    • You should also be using inheritance in your controllers for you to ensure consistency and reduce the amount of coding.

    Example: instance script at /Sample/Launcher/Default.cgi

    #!C:/Perl/bin/perl.exe use lib qw( /www/local/sw/admin/lib ); use Sample::Launcher; Sample::Launcher->new->run;

    Now you know where every controller module is based upon the name of instance script. It provides consistency.

    This would be your Main module.

    package Sample::Main; use strict; use warnings; use base qw{CGI::Application}; use CGI::Application::Plugin::AutoRunmode; sub get_tmpl_path { my $self = shift; return '/Sample/'; } sub one :STARTRUNMODE { my $self = shift; my $tmpl = $self->load_tmpl( $self->get_tmpl_path() . 'One.tmpl' ); return $tmpl->output; } sub two :RUNMODE { my $self = shift; my $tmpl = $self->load_tmpl( $self->get_tmpl_path() . 'Two.tmpl' ); return $tmpl->output; } 1;

    This sets you with some initial defaults. Now lets take advantage of this by inheriting its properties but and change the default path that is appropriate for that controller.

    package Sample::Launcher; use strict; use warnings; use base 'Sample::Main'; sub get_tmpl_path { my $self = shift; return '/Sample/Launcher/'; }

    Thats it! Your Sample::Launcher module has two run modes (One and Two), and can find the templates in '/Sample/Launcher/One.tmpl' and '/Sample/Launcher/Two.tmpl' . Notice, no redundancy! :)

    Try that and see if it is not better for you.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://612534]
Approved by jettero
Front-paged by derby
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (5)
As of 2024-04-19 03:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found