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]