[cgiapp] Model design in C::A/Titanium

Mark Rajcok mrajcok at gmail.com
Tue Oct 21 15:20:17 EDT 2008


>
> I would appreciate some feedback on a possible MVC structure for a
> Titanium/CGI::App that I am currently building. I'm using CA::Dispatch to
> allow multiple apps with a small number of rm's each. Views are handled by
> CAP::TT. Just the Model aspect is proving a little challenging. I'm trying
> to ensure that all database calls are handled outside the controllers, and
> to this end am using a module called WebApp::Model
>
> This seems to work well, but the problem is that WebApp::Model is growing,
> as ever more (unrelated) db-related methods are added. Perhaps I
> could/should sub-class this one? Ideally I would move the WebApp::Model
> methods into their respective WebApp::DB class (eg get_foo() goes into
> WebApp::DB::Foo), but then I can't access it any longer from
> $c->param('model')->get_foo.


I struggled with the "model" part of MVC recently also.  Pages like
http://cgi-app.org/index.cgi?OrganizeApp don't really address the model
part, other than to essentially say "best practice: keep DB stuff outside
the controllers".  (I plan to update that page in the near future, to
discuss how to model the model part.)

After reading through some of the archives on this mailing list, and this
thread, the consensus seems to be to create a database-aware base class that
model objects can inherit from.  Often, each "model object" models a
database table.  Normally an ORM module (Rose, Class::DBI New Code:
DBIx::Class) is used.  Creating your own WebApp::Model class is probably not
"best practice".

I decided to create my own base class, MyDB::Object.  This object is
intentionally similar to Rose::DB::Object -- it has load, save, insert,
update, and delete methods -- so that when I eventually migrate to RDBO, it
will be smoother (don't ask why I am doing it like this).  This base class
uses AUTOLOAD to create accessor methods for each field (using code from
Conway's book Object Oriented Perl).  So for example, if my User table has a
'u_password' field, I can access it like so:

my $user = User->new(u_username => 'mraj');
$user->load;  # populates object from the database
print $user->u_password;  # get pw, AUTOLOAD creates u_password method
$user->u_password('new_password');  # change pw

The User.pm class (that models the user table in the database) is rather
simple.  It in herits from MyDB::Object to get common DB operations.  It has
an array of field names, and a method that overrides the base class to
return the field names as an array ref.

I have a controller (ManageUser.pm) that inherits from CA, and it only has
runmodes that handle user account related operations (as per "Quote 3" on
http://cgi-app.org/index.cgi?OrganizeApp).  Here's a sample runmode:

sub display_user_form : Runmode {  # this runmode can only be called if
authenticated
  my $self = shift;
  my $user = User->new(u_username => $self->authen->username);
  $user->load;  # calls the base class method in MyDB::Object
  my $t = ... load your HTML template ...
  foreach my $field (@$user->fields) {  # fields is a method I wrote in
class User.pm
    $t->param($field => $user->$field);
  }
  $t->output;
}

There we go... no DB access in the controller... only the model (class User
and MyDB::Object) access the DB.

There's one wrinkle I left out... how did object $user get the $dbh that the
controller created?  Well, based on another discussion from the archives, I
created a singleton class to store the $dbh.  In cgiapp_init, I create the
singleton and pass it the $dbh:
  MyEnv->instance(dbh => $dbh);
Then the model classes only need to use MyEnv (they don't need to use any CA
clases):
  use MyEnv;
  ...
  my $r = MyEnv->instance->dbh->selectall_hashref(...)

http://perldesignpatterns.com/?SingletonPattern

Class diagram:

C:A        MyDB::Object    MyEnv   user.cgi
  |            |
MyBase        User
  |
UserManager

UserManager.pm inherits from MyBase, which inherits from CA.
UserManager 'use's model objects like User.pm.
MyDB::Object uses MyEnv and DBI.
MyBase creates the MyEnv singleton (and handles
login/logout/authentication).

Runmodes related to other operations (say inventory items), would have an
inventory.cgi instance script, an InventoryManager.pm class that inherits
from MyBase for the runmodes related to inventory, and one or more model
classes (e.g., Item.pm) that inherit from MyDB::Object.

But the main question is whether this is the right approach to start with -
> stuffing WebApp::Model into the WebApp object and retrieving it in the
> run-modes via a param_name. Advantages include decoupling the model from the
> controllers - they don't know/care how get_foo() gets its data, and I can
> change the db-related stuff (eg RDBO <=> DBIC) without affecting the
> controllers. Disadvantage (so far) is lumping all methods into one
> (WebApp::Model) module. Comments & thoughts most welcome.
>


> Have a base class of WebApp::DB;
> Have WebApp::DB::Foo inherit from base class;
> Make the base class smart enough to auto-load WebApp::DB::Foo and
> instantiate it when needed.
> A little autoloader magic goes a long way, and you get to get rid of all
> the
> "get_foo" stuff, and just do foo->get().


Personally, I don't like objects popping into existence.  I prefer to "new"
a Foo object when needed.
>From an OO perspective, it also seems odd to me that a base class could
instantiate a subclass (base classes shouldn't know about their subclasses).
Also, what if you need to create more than one Foo object in a runmode?  The
"auto instantiate" mechanism won't work.
With the code I show above, a runmode can create multiple user objects if
needed:
my $user1 = User->new(...);
my $user2 = User->new(...);

-- Mark


More information about the cgiapp mailing list