Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Review: CGI::Prototype

by dragonchild (Archbishop)
on Dec 02, 2004 at 14:15 UTC ( [id://411760]=perlmeditation: print w/replies, xml ) Need Help??

merlyn, after much ballyhoo, has released CGI::Prototype to CPAN. I decided to see what all the fuss was about, especially as he said this was an improvement over CGI::Application, which has a large following. If merlyn is going to reinvent the wheel, it's got to be something to write home about, right?

The first thing that hit me, upon reading the POD, was the version number. 0.90 for a first CPAN release? *shrugs* I've never put much stock in the big 1.00, but I know others do. Not a big deal.

The second thing was the complete lack of acknowledgement of prior art. Nowhere does he provide a rationale for a rounder wheel - he doesn't even acknowledge that there was a wheel before. This module may be the greatest thing since sliced bread, but you still have to tell me why it's a better mousetrap. Otherwise, I'm not going to give it a spin.

So, I reread the POD a little closer, and then read the source. merlyn, both in posts here and in the article referenced in the POD, mentions that he's tried C::A and found it lacking. What major improvements has he made? I think I found a bunch of differences ... it's up to you to determine if it's an improvement or not.

  1. C::A uses a subclass as a repository for runmodes. A runmode is the Controller for a given page. C::P seems to use a class as the Controller for a given page.
  2. C::A provides a number of hooks to take actions at various times. C::P also does the same thing. The names are different, but each has the same number of hooks.
  3. C::P requires the use of Template Toolkit. While TT is arguably the cadillac of templating technology, there are hundreds of reasons to stick with HTML::Template. More importantly, why can't I choose? C::A, while strongly recommending H::T, allows you to override load_tmpl(), and it's even documented how.
  4. C::A provides one loop for work, called run(). That loop calls one runmode, who is responsible for returning output. C::P provides one loop for work, called activate(). That loop calls methods in one class, then potentially calls methods in another class. The last class called is responsible for returning output from the display() method.

That last item threw me for a loop when I first read it. Why would C::P's engine loop potentially reference two classes? I realized it was for handling form input. From what I can guess, it looks like you're supposed to do something like:

  1. My::App::DisplayForm
  2. My::App::ReadForm which has a respond() of My::App::DisplayAcknowledgement

My first question was "Why?", as in "Why so complicated?". In C::A, if you want to have runmodeA use runmodeB for display, you just:

sub runmodeA { my $self = shift; # Do stuff here return $self->runmodeB(); }
Seems pretty simple, to me.

My second question immediately after was "What if my respond() wants to have a repond()\n"? In other words, why is the redispatch only one layer deep? Why wouldn't it be a loop of potential redispatch?

Lastly, I tried to redesign a medium-sized application I just finished work on a few weeks ago, taking it from C::A to C::P. And, I found I didn't want to. C::A allows me to organize runmodes into functional areas, corresponding to how the application is broken up. This means that all the pages in a certain section are in the same file. I don't have one file per page. That, to me, seems to be a huge step backwards.

And, it's not because I don't like bunches of files. It's because I cannot have standard pages across multiple applications. C::A allows me to define runmodes at any level in the class hierarchy. This means that I can have a runmode, such as a generic login form runmode, appear the same across every single application I write. This allows for easier co-branding of sites with less cost. I'm not sure how I'd do that with C::P.

Overall, I think C::P is a very well-written module by an experienced professional. But, I don't think it needed to be written. C::A already exists, it already has an excellent track record, and I personally know they have been very receptive to patches and suggestions. I would have much rather seen merlyn's experience and knowledge be used to improve C::A and take it to the next level rather than create a different color copy.

Being right, does not endow the right to be rude; politeness costs nothing.
Being unknowing, is not the same as being stupid.
Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

Replies are listed 'Best First'.
•Re: Review: CGI::Prototype
by merlyn (Sage) on Dec 02, 2004 at 14:43 UTC
    Unfortunately, I don't have time to address every point of your review, since I'm sitting in an airport and will need to leave soon. Let me address some of the more critical aspects:

    The version number is 0.90 because the internal release at the customer site is 0.85, because they're already using it in their production and putting something like "0.15" into production would have been a bit embarassing. But to me, it does feel like about a 0.35 release, if that helps.

    To acknowledge prior art would be to acknowledge every MVC framework I've seen. I didn't look at just one package. I've looked at dozens. This is not the heir apparent to CGI::Application any more than it's the descendant of Smalltalk's MVC pattern from my studies in 1981.

    For said client, I was initially steered towards CGI::Application, and started liking the externals, but then was also told to stare at the internals and I'm glad I did. Huge subroutines covering hundreds of lines of code. And not quite the right callbacks that I needed, which would have meant a careful understanding of those monolithic subroutines to cut and paste, not just override callouts for callbacks.

    What I did for the design of CGI::Prototype was Keep It Simple. Very Simple. No subroutine is longer than a dozen lines. Plenty of places to override all the built-in decisions. And yes, I went with Template Toolkit, because I hate HTML::Template, because I hate angle bracket languages in general for human editing. However, overriding engine and render would be trivial to use HTML::Template. I just don't describe it because I don't want to waste time encouraging it.

    The way you design CGI apps is probably just another style that CGI::Prototype would need a different skin for. The "one page is one class is one file" is a specialization of CGI::Prototype::Hidden that happened to work for the three projects in which I'm currently developing. But I predict that other specializations are also possible for other styles.

    So, in conclusion, I'm not rewriting CGI::Application. I'm designing a generic controller for use with CGI applications that encourages subclassing as a means of specialization. That hasn't been done yet (CGI::Application doesn't qualify, because it's too specific and not tweakable enough). I'm also developing applications with this framework and will probably continue to add other specific subclasses for other styles. Maybe even a CGI::Application compatibility layer, for example, as well as an Apache::MVC compatibility layer.

    Your review presumes some things about the purpose of CGI::Prototype that are inappropriate. I'll take the blame for not communicating that more clearly. But many of the people I've shown are excited by some of the potential of this module. So I'll take that as positive feedback, and move on.

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

      And yes, I went with Template Toolkit, because I hate HTML::Template, because I hate angle bracket languages in general for human editing.

      It must have really bothered you to write your post then, right? HTML is an angle-bracket language for human editing! It's not my favorite either but it seems silly to critique an HTML templating system for using angle brackets.

      Of course, it's easy to use to the filter option to HTML::Template->new() to allow [tmpl_var foo] if that's what you want!

      -sam

        Your module HTML::Template ist still one of the most popular ones out there. I've seen ports for python, php and ruby. It's famous! Merlyns opinion is just his personal point of view - and of course that's okay.

        best regards,
        neniro

      That hasn't been done yet (CGI::Application doesn't qualify, because it's too specific and not tweakable enough).

      When you have time, please elaborate on this statement. The cgi-app mailing list has been working on dozens of improvements for tweaking and reducing the specificity of how the internal functions work. I know - I've been involved in a lot of it. We would love to have your input on how we can improve, in as much detail as you'd care to provide.

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        When you have time, please elaborate on this statement.
        Well, here's my bottom line on that.

        When I looked at CGI::Application, I concluded what I've already said. And then I went off to commit time to make my own framework.

        Since I already have CGI::Prototype in development and deployed at a few customer sites, it's not likely that anything I say now will want me to jump back over to some future version of CGI::Application. Not gonna happen.

        If you're developing CGI::Application, feel free to steal ideas as you are inspired to do so by CGI::Prototype's design, but any hour I spend working out the differences between the two is an hour I'm not able to bill to my customers to develop the framework they've chosen on my suggestion.

        -- Randal L. Schwartz, Perl hacker
        Be sure to read my standard disclaimer if this is a reply.

Re: Review: CGI::Prototype
by hardburn (Abbot) on Dec 02, 2004 at 15:11 UTC

    I'm going to have to see C::P in actual usage before deciding, but I'm going to agree with merlyn that C::A doesn't quite go far enough. In fact, I've been working on some ideas for improving it, though I may just forgo that if C::P turns out to cover them.

    Consider a single page in a bigger web application. When you get a hit, you need to get parameters, validate them, do something with them, and return the output. In C::A, all this has to be done within the single subroutine that defines the runmode. You could have your runmode being a simple dispatcher to other subroutines, but somehow this just doesn't satisfy me.

    Also, IMHO, C::A doesn't support looping runmodes very well. For example, consider a database frontend for a club where the leader of the club enters each of the member's contact information. We can't predict the maximum number of members, so the entry page has two submit buttons: one indicating we have another member to fill in, and another for indicating we're done and should save all the information to the database.

    Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

    I haven't studied C::P enough to know if it will handle this sort of thing better, but I suspect it will.

    One other thing:

    C::P requires the use of Template Toolkit. While TT is arguably the cadillac of templating technology, there are hundreds of reasons to stick with HTML::Template.

    I was initially wary of this, too, as I prefer HTML::Template. Then I looked at C::P a bit closer and saw that TT wasn't being used as Just Another Templating System. Its features are an intrinsic part of how C::P is operating. I think it would be possible to divorce the two, but I think you'd lose a lot by using H::T instead. This may finally be the reason for me to start using TT more.

    "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

      We can't predict the maximum number of members, so the entry page has two submit buttons: one indicating we have another member to fill in, and another for indicating we're done and should save all the information to the database.

      ...

      I haven't studied C::P enough to know if it will handle this sort of thing better, but I suspect it will.

      Yes. The basic theory is that there's an app-wide "dispatch" that figures out what state you are in. Then a specific "respond" gets called, which can notice which button was pressed, and return whether to stay on the same page (return $self), or go to a new page (return $self->nametopage("next")). Whichever page gets selected for rendering then does the render thing.

      To me, this makes a lot of sense in practice. When you're editing a given page, you have one editor window open on the .tt file, which describes how it looks and what fields it returns, and another window open on the .pm file, which describes what to do with those fields and what to do next. You also add in that same file extra methods for supplying your template with data while that page is being rendered. It's all quite natural and flexible.

      And groups of pages can have common ancestors, below the top level app, so the nested hierarchy and "two level" dispatch is there through the proper use of SUPER. It's just objects. Objects work.

      -- Randal L. Schwartz, Perl hacker
      Be sure to read my standard disclaimer if this is a reply.

      Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

      Why would you not have the runmode method handle this?

      I tend to use the runmode more as a vague indidactor of the expected action anyway - it determines the kind of checks that need to be done on the input, then decides which action to perform, and then refers to an output routine. Example:

      sub make_report { my $self = shift; if (my $missing = $self->missing_fields()) { # back to submitted form, with missing fields highlighted return $self->make_form($missing); } # make confirmation screen. $self->template( 'confirm.tmpl' => $self->form_values, ); }

      This decides the output based on wether or not all data has been filled in correctly, but it isn't really very different if you check for a button instead.

      update: after reading this thread again, it seems most some people find it hard to wrap their head around the following:

      runmode != page != template

      A runmode is just a hint from the calling form about what the input data is about, and what the application should to with it. It does not describe what template should be shown.

        The update here is interesting and may get at the difference of approach between the two modules. (Caveat -- while I know C::A, my exposure to C::P is limited to this thread so far and a quick POD skim, so I may have it wrong. Apologies to the authors if so.) Both modules are effectively just state machines. However, the state-less nature of web-browsing means that tracking state and state transition is a bit more difficult.

        The C::A approach with runmodes takes the approach that the runmode specifies a target -- a desired state, or really, a desired action which is usually to reach a certain state. The application reads the desired action from a parameter and dispatches to a subroutine for that action. The subroutine checks the validity of the action given the input and presents a new state back to the user. (This may be done within the runmode subroutine or as a call to a "display_x" subroutine.) Templates really define a state and logic in the templates specialize the state for special cases (e.g. redisplay a form with errors in red). Plus, templates define the the valid state transitions (runmodes) that can be called from a given state.

        The C::P approach instead uses the information submitted to the application to determine the state the user was just in rather than the target. (Because of the state-less web, it can't assume that the new request originated from the last state displayed.) The state corresponds to a class, which examines the input to determine an appropriate state transition (from among valid state transitions for that state) and then calls a subroutine in a new class (state) to display back to the user. Classes define how to enter a state -- by displaying a template -- and how to exit -- by executing a state transition for a given input.

        Syntax and templating systems aside, this conceptual difference seems to me to be the crux of the issue and preference for use may depend on how a programmer likes to think about it. In C::A, one defines state transitions and uses those to determine a new state to display. In C::P, one defines states and how a user gets from that state to other valid states.

        Polymorphism becomes important in C::P as a way of handling special cases (e.g. a form with errors) without repetitive coding, whereas with C::A, the special case coding would more likely happen procedurally in the templates.

        Hope that helps some people. (Just writing it was helpful to me to bend my mind around how to conceptualize states and transitions for CGI in general.)

        -xdg

        Code posted by xdg on PerlMonks is public domain. It has no warranties, express or implied. Posted code may not have been tested. Use at your own risk.

        A runmode is just a hint from the calling form about what the input data is about, and what the application should to with it. It does not describe what template should be shown.

        Exactly my philosophy when working with C::A. I tried to factor this into a plugin that makes it very easy to just show templates (by using path_info to figure out which template to show and having associated data files for the templates so that they can "render themselves") so that the runmodes can concentrate on doing "the business logic".

      Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

      When I do this kind of thing, I put the logic in the handling runmode ... something like:

      # This runmode show the data-entry template sub show_entry { # ... yada yada yada } sub process_entry { my ( $self, $query ); $self = shift; $query = $self->query(); # This method will add the entry in the db $self->add_entry(); # Now if the user hit the 'add another' button... if ( $query->param( 'addanother' ) ) { return $self->show_entry(); } else { # mainscreen() being the main/home page runmode return $self->mainscreen(); } }

        When I do this kind of thing, I put the logic in the handling runmode

        I don't think this will work very well. Often, mainscreen() itself took input from some other form. Now I'll need to pile on yet more logic inside mainscreen() to know which runmode it's being called by.

        I don't see a solution with C::A that doesn't involve pileing on a lot of additional logic. It seems very good for applications that have a simple, linear progression through the pages (start -> A -> B -> C -> finish). It breaks down fast for more complex structures.

        "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

      Our app will have to figure out which button was hit and handle the data and output as needed. In C::A, there is really only one place you can put this logic: cgiapp_prerun(). If you have many runmodes that need this sort of functionalilty, your cgiapp_prerun() can start getting rather large.

      Why do both buttons go to the same runmode when they don't do the same thing? I would have the first button have a small Javascripty-doo that changes the hidden runmode field from ActuallySubmit to AddAnother, then submit. Then, you let C::A do the work of determining which button was hit using its native dispatching mechanism. No need for any cgiapp_prerun() nonsense. You're not letting C::A do what it's supposed to do.

      When you get a hit, you need to get parameters, validate them, do something with them, and return the output. In C::A, all this has to be done within the single subroutine that defines the runmode. You could have your runmode being a simple dispatcher to other subroutines, but somehow this just doesn't satisfy me.

      There are dozens of plugins that do exactly what you're trying to have the runmode do. Form validation, parameter getting, etc ... all of these are solved problems.

      I'm going to agree with merlyn that C::A doesn't quite go far enough.

      Please elaborate further - WHAT doesn't C::A do?

      Being right, does not endow the right to be rude; politeness costs nothing.
      Being unknowing, is not the same as being stupid.
      Expressing a contrary opinion, whether to the individual or the group, is more often a sign of deeper thought than of cantankerous belligerence.
      Do not mistake your goals as the only goals; your opinion as the only opinion; your confidence as correctness. Saying you know better is not the same as explaining you know better.

        I would have the first button have a small Javascripty-doo that changes the hidden runmode field from ActuallySubmit to AddAnother, then submit.

        dragonchild, I've never used C::A (or C::P), but why must the runmode be in a hidden field? Why not have the submit button be the runmode value? For example:

        <form action="blah" method="post"> <input type="text" name="data" value="Default data." /> <input type="submit" name="runmode" value="Save" /> <input type="submit" name="runmode" value="Add more data" /> </form>

        If the "Save" button is clicked, the "Save" runmode will be called. If the "Add more data" button is clicked, that runmode will be called.

        The only limitation is that either your button text or runmode names will be ugly.

        Please elaborate further - WHAT doesn't C::A do?

        Well, it hardly does anything at all, wouldn't you agree? It's just a dispatch table. All of the interesting stuff is in plugins to add Data::FormValidator and friends. That's not a negative to me. I think most people are going to build more on top of C::A if they use it for anything significant.

        I would have the first button have a small Javascripty-doo that changes the hidden runmode field . . .

        Where I work, we have a significant number of users with JavaScript turned off (and it's staying off, as a lot of them are government users in departments with standing policies of not having JavaScript on).

        "There is no shame in being self-taught, only in not trying to learn in the first place." -- Atrus, Myst: The Book of D'ni.

        You can set the runmode via the submit button provided you make the value of your run mode "human readable". Something like:
        <input type="submit" name="rm" value="Update users" >
        So no javascript required.
      I haven't looked at Merlyn's module yet, but I will.

      CGI::Application can be quite painful for making lots of apps do different things, and I didn't like it. I wrote my next app using RubyOnRails because of my experience with CGI::Application. Yes, C::A was a huge improvement over raw CGI when I first found it, but it still made coding un-fun and felt a little raw.

      There is NEVER anything wrong with reinventing a wheel when you think it's square, after all, you personally have to use that wheel. I am a *huge* proponent of clean code and good design ... wherever it can be found. Honestly though, I doubt I'm leaving rails anytime soon.

Re: Review: CGI::Prototype
by jerlbaum (Sexton) on Dec 03, 2004 at 00:33 UTC
    Interesting discussion. I must say, I'm quite flattered that Randal Schwartz would take such a personal interest in my little bit of code.

    While I am sure that his module will no doubt correct all the shortcomings in my CGI::Application, I worry that he might not be able to compete with the idyllic simplicity of CGI::Application::Plus. <g>

    -Jesse-

      Don't be too flattered :) See merlyn likes to be able to say "I didn't like this other thing, so I wrote my own" without looking too hard and C::A made an easy target (easy).
        File::Finder one-upped File::Find quite nicely. His replacements have usually been good. I tend to look at hold his modules, Damian's, Simon Cozens', and Brian Ingerson's a little higher in regard ... but only because respect is earned. They write things that do good work for me. Note: I'm not Merlyn.
Re: Review: CGI::Prototype
by perrin (Chancellor) on Dec 02, 2004 at 15:35 UTC
    I've never understood why CPAN authors put their module numbers so low. If the API is complete and it has no known bugs, it is a 1.0 release! I've actually heard developers who do not follow CPAN closely balk at using very common and well-known modules because they have version numbers like 0.17. That's ridiculous.
      Programmers count from 0. :-)

      All versions of my (only) CPAN module had no known bugs when I released them (and the test code to show that the known bugs where fixed). That didn't really mean they had no bugs.

      You point about API changes is valid, but that an API is "complete" doesn't mean it's "done". It takes a LOT of testing and tweaking to get an API "just right" - so I myself prefer to keep the right to make "minor" changes to an API for quite some time.

      The 1.0 version number is arbitrary and for marketing purposes only. The associated software is never bug-free, never finished, and ususally deprecated within a couple of weeks. Programmers should know better than to trust a version number.

        All versions of my (only) CPAN module had no known bugs when I released them (and the test code to show that the known bugs where fixed). That didn't really mean they had no bugs.

        I'm not sure where you're going with that. Everything has bugs.

        You point about API changes is valid, but that an API is "complete" doesn't mean it's "done". It takes a LOT of testing and tweaking to get an API "just right"

        No API is ever done. They always evolve over time. If you do something drastic that breaks backwards compatibility in a big way, you can release 2.0.

        The 1.0 version number is arbitrary and for marketing purposes only.

        Right, which is why everyone should feel free to use it.

        Programmers should know better than to trust a version number.

        And yet they don't. They get scared by low version numbers. And so do their managers. That's why it matters.

      My first version usually is 0.01. And the one after that is 0.02. I continue adding 0.01 for every subsequent release, unless a large portion was rewritten (bump to the next x.x0), or almost everything was rewritten (bump to the next x.00).

      But if a module is at 0.xx even though it's in production use, profiled well, and has no known bugs *after a lot of use*, I might add 1 to the version, so that 0.24 can become 1.24. This is because users do indeed for some reason appear to think that the version number actually has meaning.

      Well, unless you know how a certain author uses the digits available, IT DOESN'T MEAN A THING. Proof: File::Slurp.

      The only rules are that a newer version must have a higher version number, and that the CPAN indexers understand your format.

      Juerd # { site => 'juerd.nl', plp_site => 'plp.juerd.nl', do_not_use => 'spamtrap' }

      I've never understood why CPAN authors put their module numbers so low.

      I'd guess that, at the beginning, it's a confidence issue. I'd think of it as, hey, I'm posting a module that seems to work for me, but it hasn't been widely tested yet: let's not get everyone's hopes up. After that, well, small incremental changes don't really merit a major version number increment, do they?

      I agree with you; it's always rather odd to see stable, mature software with a sub-1.0 version number (slrn, anyone?). Of course, there's plenty of "production" software that really shouldn't be out of alpha yet.

      --
      Yours in pedantry,
      F o x t r o t U n i f o r m

      "Anything you put in comments is not tested and easily goes out of date." -- tye

      My api are numbered by cvs :>
        That can get you in trouble. CPAN version numbers need to be parseable as real numbers, so to CPAN 1.10 is an earlier release than 1.9.
Re: Review: CGI::Prototype
by astroboy (Chaplain) on Dec 04, 2004 at 10:37 UTC
    I'm a big fan on C::A, but one thing that caught my eye at the OSDC/YAPC::AU conference was CGI::Unified. Unfortunately, the 1/2 hr slot wasn't long enough for the Franck Porcher to demonstrate all of its features, but what I saw looks tasty. He intends putting an updated version on CPAN around XMAS. The abstract's at http://www.osdc.com.au/public/index.cgi?SpeakerAbstracts. The paper's not online yet, but I believe that the organisers will be making all of the papers available in the near future.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others imbibing at the Monastery: (5)
As of 2024-03-29 07:49 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found