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

I wrote this up, and then saw CGI::Application with 'main' runmodes and 'sub' runmodes. Very related, but I still have some specific oddities, so I am going ahead and posting this anyway.

I am trying to organize my CGI::Application thusly because I want to create a common Account.pm that many different instances of web applications can draw upon. In fact, I want to package even some basic templates within Account.pm that can be automatically generated and used within more customized application-specific templates. So, when everything is working, I would have a common Account.pm somewhere in my person lib that I would be able to include in my web application instance and get all the account authentication code automagically.

web_root + - index.cgi + lib - App.pm - Account.pm - ... + templates - welcome.tmpl - login.tmpl - ... # in index.cgi ########################## use lib "lib"; use App; my $c = App->new( TMPL_PATH => "templates", PARAMS => { .. } ); $c->run(); ##################################### # in App.pm ########################### use base "CGI::Application"; use Account; sub setup { my $self = shift; $self->start_mode("view"); $self->mode_param("a"); $self->run_modes( "view" => "view", "edit" => "edit", .. "verify" => "Account::verify", "login" => "Account::login", "logout" => "Account::logout", "create account" => "Account::create", ); } sub view { my $self = shift; if ($self->session->param("~logged_in")) { # View stuff } else { $self->login; } } ##################################### # in Account.pm ######################## package Account; sub login { my ($self, %args) = @_; # login stuff $self->view; } sub logout { my ($self, %args) = @_; # logout stuff $self->view; } #####################################

With the above, of course, I get the error that there was an Error executing run mode 'view': Can't locate object method "login" via package "App" at.... If I change $self->login inside sub view {} to $self->Account::login then it works, but calling stuff from within Account.pm stops working.

This seems pretty basic to me, yet, embarrassingly for me, I can't figure out a solution. Is there a better way of doing this? Why doesn't $self->login work even though it is declared correctly inside setup?

--

when small people start casting long shadows, it is time to go to bed

Replies are listed 'Best First'.
Re: Modularizing CGI::Application
by Herkum (Parson) on Apr 28, 2007 at 15:51 UTC

    Because you are doing this,

    $self->login

    But you did not declare the login, logout methods in the App.pm module, you declared them in the Account.pm module.

    The quick and dirty way to fix it is to say,

    use base 'Account';

    A better solution would be to use CGI::Application::Plugin::Authentication and the CGI::Application::Plugin::Session plugins to do authentication. It is a little more work to get it right, but it is a more robust once you do getting working

      Thanks, that did the trick (for now). I am indeed using CGI::Application::Plugin::Session, but am not using CGI::Application::Plugin::Authentication because it seems unnecessarily complex, and possibly difficult to customize to my needs. But I will look at it again carefully.

      One question though -- why should I have to declare the login/logout methods in App.pm? My logic told me that defining them in sub setup  .. } should have take care of that automatically? Obviously my logic was wrong. I want to know why? Does this mean that if I create more custom modules to package related functionality together, I would have to do the use base " .. "; bit for each one of them?

      --

      when small people start casting long shadows, it is time to go to bed

        why should I have to declare the login/logout methods in App.pm? My logic told me that defining them in sub setup

        Declaring methods in the setup() only says what run-modes/methods can be executed. It does not say where those methods are, since you are putting them into the Account package but you are attempting to use them as a method in the App package the program cannot find them.

        By using 'base' you are saying, all the methods that are a part of the Account class, make them a part of the App class as well. So the program runs it will check App class first for a login() method, it cannot find it. It checks the Account class for a login() method, finds and then runs it, all is well! :)

        Does this mean that if I create more custom modules to package related functionality together, I would have to do the use base

        basically, yes. It is not easy to grasp at first, I know because I struggled with it too. Try it a couple of time and eventually you will get around to figuring it out.