On Wednesday, January 23, 2002, at 11:06 PM, Jesse Erlbaum wrote:

>
> Mason is a server-page system.  It is, architecturally, no 
> different from
> EmbPerl, ASP, JSP or ColdFusion.  CGI::Application is what I 
> would describe
> as an "application-oriented" architecture.  Combined with a templating
> system, such as HTML::Template, it is a complete solution.
> -Jesse-
>

This is what I used to think, but something very similar to 
CGI::Application is coming out for Mason soon, here is a recent 
post from the Mason list:

From: John Siracusa <[EMAIL PROTECTED]>
Date: Mon Jan 21, 2002  04:08:54 PM US/Eastern
To: Mason Users <[EMAIL PROTECTED]>
Subject: Re: [Mason] Mason for views only (again)
Reply-To: [EMAIL PROTECTED]

On 1/21/02 2:11 PM, Ken Miller wrote:
Experiements into MVC and Mason continue....

I must have missed this thread starting, but I thought I'd drop a note
describing how I (sort of) MVC-ed a Mason site.

First, I mostly use Mason for the view side of things.  My unit of code
reuse is the Perl module, not the Mason component.

So my model objects are all Perl objects.  My controller(s) 
is/are in one
big "app object" that implements the dispatch logic based on a requested
action.

The app object is exposed to mason via a simple app.mc file 
something like
this:

     <%perl>
     $app->refresh();

     $app->params(\%ARGS);

     $app->run();
     </%perl>

     <%once>
     my $app = My::App::Users->new();
     </%once>

(If I don't want to keep the whole app object around, I can get 
rid of the
<%once> block and change the call to refresh() into a call to new().)

I'm a URL stickler, and I can't stand to see stuff like:

     http://foo.com/bar/baz.pl?a=go&b=blah&q=foo

Ick.  I want my URLs to look like:

     http://foo.com/user/info
     http://foo.com/user/permissions/edit

Etc. etc.  No file name extensions, and no arguments when possible.  I
differentiate between actions and views by using an action prefix 
like "bin"
or "exec" in the URL.  I stick it at the front of the URL, but it 
could go
in several places.  Some examples of "action URLs" (these would 
be placed in
<form> tags' action=... attributes):

     http://foo.com/exec/user/create
     http://foo.com/exec/user/delete
     http://foo.com/exec/user/permissions/update

Through the magic of URL translation, the files at the end of those URLs
could be anything.  I tend to use .pl files for actions and .html 
files for
views.  Anyway, the files that are view-ish and look something 
like this:

Inside /user/info/index.html

     <& '/header.mc', title => blah', ... &>

     <h1>Blah Blah</h1>

     ...

     <& $app, action => 'info', %ARGS &>

     <& '/footer.mc' &>

     <%once>
     my $app = My::Site->app_path('users');
     </%once>

(where the My::Site->app_path(...) bit is just an abstraction to keep my
options open about where I put the app .mc file.)

That's a view-ish file.  A more action-oriented file wouldn't have any
view-ish formatting.  For example, here's /exec/user/delete.pl

     % $m->comp($app, action => 'delete', %ARGS);

     <%once>
     my $app = My::Site->app_path('users');
     </%once>

So each component located at a "descriptive URL" is 
"pre-programmed" to do
some sort of action that corresponds to its URL.

Finally, "form objects" are used inside the app object to further 
separate
content from presentation.  An example of a form object would be
"My::Site::User::Form::Edit", which would be a form that provides 
all the
fields for editing a My::Site::User object.

Each form object contains field objects which may be made up of 
one or more
actual (HTML) field elements (e.g. A compond month/day/year field).

The My::Site::User::Form::Edit form object can initialize itself 
based on a
My::Site::User object, and can produce a My::Site::User object 
corresponding
to the current state of the form.

Each field object (and, in turn, each form object) has 
validation, coercion,
and filtering methods, etc. etc.

This all makes the code in the app object much simpler.  For example,
creating a user in an app object method looks something like this:

     sub create_user
     {
       my($self) = shift;

       my $user_form =
         My::Site::User::Form::Edit->new(
           params => $self->params,
           action => '/exec/user/form/create',
           method => 'post');

       $user_form->init_fields();

       unless($user_form->validate)
       {
         # ...handle errors
       }

       # Get user object from current state of the form
       my $user = $user_form->user_from_form;

       my $bo = My::BackOffice->new;

       if($bo->create_user($user))
       {
         # Create succeeded
         $self->redirect('/user/list');
       }
       else
       {
         $self->return_error("Could not create user - ", $bo->error);
       }
     }

Finally, simple view components also deal only with model 
objects.  Here's
the user-list method of the app object:

     sub list_users
     {
       my($self) = shift;

       my $bo = My::BackOffice->new;

       my @users;

       unless(@users = $bo->get_users(...))
       {
         $self->error($bo->error);
       }

       $self->run_comp(comp => '/user/list/default.mc',
                       args =>
                       {
                         users => \@users,
                         error  => $self->error,
                       });
     }

Here's the main user list file sitting at /user/list/index.html:

     <& '/header.mc', title => 'User List', ... &>

     <h1>Users</h1>

     ...

     <& $app, action => 'list', %ARGS &>

     <& '/footer.mc' &>

     <%once>
     my $app = My::Site->app_path('alert');
     </%once>

The /user/list/default.mc that handles the "view" part of the 
actual user
list looks something like this:

     <% $error %>

     <%perl>
     if(@$users)
     {
       $m->out(<<"EOF");
     <table>
     <tr>
     <th>Name</th>
     <th>Email</th>
     <th>Status</th>
     </tr>
     EOF

       foreach my $user (@$users)
       {
         $m->out("<tr>\n" . join("\n", map
                 {
                   '<td>' . Apache::Util::escape_html($_) . '</td>'
                 }
                 $user->full_name,
                 $user->email,
                 ($user->is_active) ? Active' : 'Inactive') .
                 "</tr>\n");
       }

       $m->out('</table>');
     }
     else
     {
       $m->out('(No users defined yet.)');
     }
     </%perl>

     <%init>
     my $error = $ARGS{'error'};
     my $users = $ARGS{'users'} || [];

     if($error)
     {
       $error = q(<span class="error">ERROR: Could not list users - ) .
                Apache::Util::escape_html($error) .
                q(</span><br>);
     }
     </%init>

So, the model objects are completely reusable across multiple app 
and form
objects, and all the mason components and HTML files deal only with app
objects, form objects, model objects, or the output of other components
(aggregation).

It may seem a little "heavyweight" at first, but it was designed 
for maximum
flexibility, and the amount of code sharing is tremendous.  The 
app object
and form object base classes, for example, contain 90% of the code any
simple app or form object will need.

I find than handling very simple logic and doing content aggregation and
formatting is Mason's "sweet spot" for me.  I know some may think 
it looks
like I'm "barely using Mason" (or some may cringe at stuff like 
"my $users =
$ARGS{'users'} || [];"), but this is the technique and style I 
settled on
after many tries.  YMMV :)

(And yes, I may put my form object code up on CPAN if I ever get 
the time
and energy... :)

-John


_______________________________________________
Mason-users mailing list
[EMAIL PROTECTED]
https://lists.sourceforge.net/lists/listinfo/mason-users



---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to