in reply to Problem with Mason, can it be solved with a subrequest?

This is the problem that I see with a templating only solution. Ie. all your logic is interleaved with the display and sometimes you figure things out too late.

This answer is, I admit, not so much a solution to your current problem, but rather a (very) different way of designing your web app that stops this being an issue.

The way I do it with perl/mason (which is not necessarily the best way) is to have a two pass apporach to every page. I borrowed this idea from the AOLServer templating system (and others) where you have a seperate template and code page.

In essence, each web request to one of my systems first runs a logic script (actually it instantiates an object of the right class, but you don't have to use OO) and then renders a template.

This may not be clear, so lets take an example:

  1. Someone requests mysite.com/foo/bar
  2. my custom request processor code (which could be run by the mason_handler or the autohandler or a dhandler, depending on your setup) determines that it should instantiate an object of type My::Site::Web::foo::bar
  3. After successful instantiation, it passes the %ARGS from any form post into a method called (by convention) run_page which implements all the non-display logic for that page. The convenience here of using OO is that this method can be subclassed to allow sharing of common page tasks like security management etc.
  4. Note that the object is also passed (or has access to a global) variable %session or similar, to access a session hash managed by Apache::Session
  5. If run_page returns true, then the request processor renders the web page with the mason component /foo/bar
  6. The mason component has access to both the %session hash, but also to another mason global that I name $page which is (in this case) the My::Site::Web::foo::bar object
That is the way a normal page render works. In the case where, say, an error occurs or someone is lacking the required permissions, then the request processor can instead render (or redirect to) a different template eg. an error page.

There are a lot more subtleties to my actual implementation (which I hope to be able to release to CPAN with the appropriate authorisation), but the basic idea of processing logic first, and rendering the template second is relatively easy to implement using mason, and gives you a lot more power in situations like this.

I know that mason has it's own way of allowing method calls within the mason framework, but I believe it was Randall Scwhartz (or possibly Ken Williams) who said (on the mason mailing list) something like "if something can be done in a perl module, it probably should".
  • Comment on Seperating logic from web display using mason

Replies are listed 'Best First'.
Re: Seperating logic from web display using mason
by EvanCarroll (Chaplain) on Oct 02, 2005 at 02:00 UTC
    Thats exactly what I'm currently doing.
    1. page in /auth_required/ is requested
    2. /auth_required/autohandler invoked
    3. reads _session_id from cookie with Apache::Cookie;
    4. checks for session with cookie's _session_id (uses Apache::Session::Pg)
    5. validity: double checks that user_id recorded in session log is still in userdb
    The problem is the login forum is at /index.html, and the autohandler that all of my modules requireing authentication are wrapped in is in /auth_required. I believe I have solved the problem the solution I'm currently employing is to set up my <%attr> section as so:
    <%attr> http_title => 'Welcome' # Invalid user name or password OR username deleted from table 101 => 'Invalid username or password' # No cookie sent to server 201 => 'An error has occured' # The session id from cookie was invalid 301 => 'An error has occured'
    Then I set up <%args> section as so:
    <%args> $name_user => undef $error => undef </%args>
    Then from autohandler I clear the gunk out and make a code request:
    $m->clear_buffer; $m->subexec('/index.html', error => 301 ); $m->abort;
    Then inside of /index.html:
    <%method .non_auth> <div class="small_text"> <h1> <% $m->base_comp->attr_if_exists( $m->request_args->{'error'} ) +|h%>. You are not logged in. </h1> If you don't have a login, Click <a href="/create_user.html">here< +/a> to create one. </div> <form method="post" action="/index.html"> <table class="form_fields"> <tr> <td>User Name: </td> <td><input type="text" name="name_user" /></td> </tr> <tr> <td>Password: </td> <td><input type="password" name="password" value="" alt="Requi +red only for admins"/></td> </tr> </table> <div style="text-align:right"> <input type="submit" value="Proceed" /> </div> </form> </%method>
    Working for the time being, any better ideas? I usually follow MVC to a good extent.


    Evan Carroll
    www.EvanCarroll.com
Re: Seperating logic from web display using mason
by aufflick (Deacon) on Oct 02, 2005 at 12:33 UTC
    The key that I didn't make clear in my original reply is that the object instantiation and method call is all done before any data has been sent to the browser. In my case it is actually instigated from the mason handler (aka mod_perl handler), but it could just as easily be the <%init> block of the autohandler.

    Once you have started rendering components you hit the class of problem you are referring to. Another similar issue is that you might only determine (& flag) form problems in the component that defines that actual form (nice encapsulation), but then you're past the point where you could display an error summary at the top of the page (eg. "please review the 3 errors below).

    So as well as keeping logic and display seperate, I aim to have all the logic executed before any rendering commences - that way all parts of the page render have access to (or can be influenced by) all of the application logic.

    This approach can save you from a whole class of tricky situations which mason alone doesn't really solve. Mason is really powerful for templating, includes, managing the interaction with mod_perl/apache etc. What it doesn't give you is an application framework.

    I coded my own, for various reasons, along the lines described above. You can get basically the same effect with MasonX::WebApp for free from CPAN! The subtitle in the POD is "Works with Mason to do processing before Mason is invoked" which is a quick way of saying what I've been waffling on and trying to explain in these replies :)