Beefy Boxes and Bandwidth Generously Provided by pair Networks
Keep It Simple, Stupid
 
PerlMonks  

Handling different requests for a web app in one centralized place.

by geekgrrl (Pilgrim)
on Apr 30, 2004 at 22:18 UTC ( [id://349536]=perlquestion: print w/replies, xml ) Need Help??

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

I have this complex search and analysis web interface for genetic sequence data. About 6 different tools have forms that submit to one large Mason script. Things like doing different types of searches, viewing alignments, adding to folders of saved data, etc. The mason page figures out what is going on and calls the appropriate object and runs that code. But there is also a lot of if statements, session altering/updating, caching etc.

This page is rather nasty, despite my efforts at slimming it down. Is there a pattern that is useful for this type of situation? What do you do in your web apps? My goal is an easily maintainable, minimally Mason piece of code.

  • Comment on Handling different requests for a web app in one centralized place.

Replies are listed 'Best First'.
Re: Handling different requests for a web app in one centralized place.
by William G. Davis (Friar) on Apr 30, 2004 at 22:45 UTC

    Yes there is: Table-driven programming.

    For example, have several different sub-components that each do all of the input processing and HTML generation for a single page, then build a big lookup table (hash) that stores each sub-component name, then have the appropriate sub-component called using some query string parameter you embed in each form. In this example, I use the "op" parameter to tell us what operation (page) we're going to execute, then go look it up in the table, and if it exists, we execute it:

    my %sub_components = ( default_page => 'defaultpage.comp' full_text_search => 'fulltextsearch.comp', filename_search => 'filenamesearch.comp', view_alignment => 'viewalignment.comp', add_folder => 'addfolder.comp' ... ); if ($op) { if (exists $sub_components{$op}) { # execute the appropriate sub-component: eval { $m->comp($sub_components{$op}, %ARGS) }; if ($@) { # handle die() from the sub-component... } } else { # handle a request with "op" set to an invalid operation... } } ... <%args> $op => 'default_page' </%args>

    Notice, you have to pass the query string arguments you received by hand to the sub-component you excute using %ARGS. (You have to do this *even* if the sub-components are all defined in the same file as the top-evel component.) Another option is caller_args(), but that's kind of hack-ish.

      This is good plan.

      Beyond that it reminds me a good bit of building a controller, from the classic Model-View-Controller Pattern. It doesn't look much like a mason controller though because it is bypassing all the normal name and handler based dispatching. Anyhow I was wonder if anyone had adapted the normal mason handling to be more MVC like, and through the glory of google I found that someone had.

      Not that I'm suggesting anyone follow the example, merely marveling at it's existance.

      How is "table-driven programming" any different than a simple dispatch table? How is your dispatch table any different than the framework that CGI::Application provides?

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

        It's not my post, but I did recommend the strategy. I did so based on my belief that it basically is a dispatch table, and that you'd be rebuilding a bit of CGI::App.

        CGI::App is a good module, but I wouldn't bringing it into a mason application, particularly for use on just one page. Sometimes rolling your own is the best solution, particularly when it's something as simple as a dispatch table.

Re: Handling different requests for a web app in one centralized place.
by nmcfarl (Pilgrim) on May 01, 2004 at 00:27 UTC

    On the general topic of refactoring (here's a good node: Refactoring) I like to start small. Find features that can be safely moved to their own components (or modules if they are all logic, no display). Move them. Test them. Repeat.

    I find that things become easier when there is less code. This means the more code you move out, the more code you can move out. And at some point you are down to the point where you just need to clean up what you have left, and call it good.

Re: Handling different requests for a web app in one centralized place.
by saintbrie (Scribe) on May 01, 2004 at 03:05 UTC

    Seems like you can think of components as methods. Not being familiar with Mason (I use template toolkit, which is an analogue), I suspect that just breaking things down into smaller components could vastly simplify things for you.

    Factor out the 'logic' into components, and call the components. Sounds like a fairly straightforward task.

Re: Handling different requests for a web app in one centralized place.
by geekgrrl (Pilgrim) on May 01, 2004 at 23:04 UTC
    The replies are all very helpful so far - thanks. I will do more research into table-driven prog. and MVC on monday. I thought it might also be useful to offer some more detail on my page o' code. Here's an outline as I can remember it on a sunny Sat. afternoon at the local lib. on monday i might post a few snippets of code for review...

    # all the args are accessed via %ARGS. so sometimes some of these values need initializing to a default value.

    # some more messing around with args, and calling of sub components (you know, the ones that look like <% .download %>, or something like that. Its the weekend - i can't promise to have perfect syntax.)

    # I have a .working_set component, (where the user saves data - this component updates, deletes, or empties these sets).

    # Also I have a .download component that handles when the user wants to download, say, an Excel file. For some reason, I felt it necessary to keep those two within the scope of the page, rather than move them out into their own components.

    # are they viewing Previous Search Results? - if so, use the mason cache.

    # call a factory class that determines what action we are taking (search, alignment, etc.) and returns the appropriate object.

    # prepare parameters to pass into this new object when we call the method prepare_results

    #call the object method mentioned above.

    # now, update the session with any altered values that happen as a result of calling the method.

    # look at the results. call the appropriate component - check for error or warning message, and call that component if necessary.

    #otherwise, call component assoc. with object.

    # update session to have the last set of ARGS in case we have to go back.

    # if the action was a search or genome search, save the search results in the mason cache.

    And at this point, I go take a coffee break.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (3)
As of 2024-04-18 23:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found