> > 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 ##### CGI::Application community mailing list ################ ## ## ## To unsubscribe, or change your message delivery options, ## ## visit: http://www.erlbaum.net/mailman/listinfo/cgiapp ## ## ## ## Web archive: http://www.erlbaum.net/pipermail/cgiapp/ ## ## Wiki: http://cgiapp.erlbaum.net/ ## ## ## ################################################################