Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

milardj (4452)

milardj
  (email not shown publicly)

Journal of milardj (4452)

Tuesday December 21, 2004
09:35 PM

My pet solution: Web framework

[ #22410 ]

Amongst other things I intend to use this journal to discuss the design of existing and new projects and frameworks I have worked on. I tend to design in a vacuum these days (at my old job I was usually hooked up with a partner during design which I found to be educational and led to stronger designs - we both approached the problem from different angles and the design really benefited from that) and I miss having a feedback loop to point out missed opportunities and more effective strategies.

I'm a full time programmer who also operates a consulting/contracting company (very small scale) on the side. About 2 1/2 years ago I was contracted to create a web app which would allow:

  • Uploading of data files (typically market research results) into a database
  • Definition and generation of crosstab reports
  • Definition and generation of regular reports
  • Project, user, and client management

I decided on a framework that would be driven by XML. I borrowed the run mode concept from C::A and defined an XML file that would control the "steps" needed for each run mode:

    <rm name="login" next="main.html" on-error="login.html">
         <step handler="login"/>
    </rm>

"steps" could be predefined sql actions (see below) or delegated to a handler as shown above. "handlers" are defined in another xml file and are subroutines that are passed the CGI instance and the database handle:

<handler name="login">
    sub {
           my ($cgi,$dbh) = @_;
           #-- Do some stuff

           return $cgi;
        }
</handler>

I decided on this approach because I found that a lot of the perl code I needed as handlers were only a couple of lines long and I didn't feel the need to create a package.

In a questionable decision I decided to just pass the CGI instance throughout the framework. Any part of the framework could add new params to the instance or modify existing params. At the very least I should have created a wrapper to CGI. That is one of the items on my To Do list.

As mentioned previously "steps" could also be predefined SQL actions:

    <rm name="foo_search" next="foo.html">
         <step type="get" action="get_foo" using="foo_table.xml"/>
    </rm>

Where foo_table.xml contains all the SQL related to the foo table:

<sql name="get_foo">
        <statement>
                  Select col1, col2, col3 from foo
        </statement>
        <where>
                 where x = ? and y = ? and z= ?
        </where>
        <values>
            <column db_name="col1" alias="cgi_param_name1"/>
            <column db_name="col2" alias="cgi_param_name2"/>
            <column db_name="col3" alias="cgi_param_name3"/>
        </values>
        <bind>
            <column alias="x_alias"/>
            <column alias="y_alias"/>
            <column alias="z_alias"/>
        </bind>
        <struct alias_key="col1+col2" type="nested" cgi_param="foo_struct"/>
    </sql>

When RM.pm (the module which processes the run modes) sees a SQL step (type = get|delete|insert|update) it calls DAO.pm which is responsible for database interactions. DAO will execute the SQL and determine how to bundle up the result set. In this case because we defined the

<struct>

tag we are telling DAO that we want a more complex data structure returned. The struct tag instructs DAO.pm to return a hash where the key is the concatenation of the columns defined by the key attribute. The structure would be passed in $cgi with the param name "foo_struct" as defined by the cgi_param attribute in the

<struct>

tag.

If our result set is:

COL1      COL2    COL3
AAAA      11111   11111
AAAA      22222   12121

Then our return structure is:

{ "AAAA~11111" => { "COL1" => AAAA
                    "COL2" => 11111
                    "COL3" => 11111 },
  "AAAA~22222" => { "COL1" => AAAA
                    "COL2" => 22222
                    "COL3" => 12121}
}

If we specify type="nested" then the return structure is:

{
    "AAAA" => {
                11111 => {
                            "COL1" => AAAA
                            "COL2" => 11111
                            "COL3" => 11111
                         },

                22222 => {
                            "COL1" => AAAA
                            "COL2" => 22222
                            "COL3" => 12121
                         },
              }
}

By default, if no

<struct>

tag is defined, then each column is added to the $cgi instance where param name is equal to the alias attribute.

Run mode definitions can also import other defined run modes:

    <rm name="something_common">
            <step handler="always_need_this"/>
            <step handler="and_this"/>
   </rm>
    <rm name="foo_search" next="foo.html">
         <step type="get" action="get_foo" using="foo_table.xml"/>
         <step include="something_common"/>
    </rm>

We can also iterate run modes (or steps) when needed. For example say we have a multi select box and we want to execute a step for each highlighted option:

<rm name="delete_user" foreach="delete_user_list" set="user_id" next="userMgr.html">
        <step type="delete" action="delete_user" using="user_table.xml"/>
    </rm>

This run mode will iterate through $cgi->param("delete_user_list") set $cgi->param(-name=>"user_id", -value=>current id) and perform a delete for each user.

We can also iterate at the step level if some steps have to be executed for a list and some only once:

rm name="delete_user" next=" userMgr.html">
        <step foreach="delete_user_list "
                  set="user_id"
                 type="delete"
               action="delete_user"
                using="user_table.xml"/>
        <step handler="foobar"/>
    </rm>

So in summary - every interaction with the server is defined by a run mode. A run mode can consist of 0 or more steps (if 0 then we are essentially only setting the next page to serve up). Each step can consist of a predefined database interaction or be delegated to a handler.

I've found this to work very well for my purposes and I've been able to add many screens without having to write any new code at all. The SQL xml files are auto-generated by another program I wrote so some new screens (for example search/result/detail screens) can be created by simply defining the templates and by defining the run modes and steps. Since they all follow the same pattern I simply copy the existing run modes, change the template name, change the action names, and I'm done.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.