Re: OOP design related question.
by Arunbear (Prior) on Sep 09, 2005 at 17:19 UTC
|
I would use Class::DBI to build a module MyApp::Db whose sole job is handling all database work (your add/delete/update logic).
package MyApp::Db;
use base Class::DBI;
__PACKAGE__->connection('dbi:mysql:dbname', 'username', 'password');
1;
package MyApp::Db::User;
use base MyApp::Db;
__PACKAGE__->table('user');
__PACKAGE__->columns(All => qw/id username password/);
...
Then your RunModes:: only need to use MyApp::Db objects to do database operations, and there is no need to pass a $dbh around.
package RunModes::User;
use MyApp::Db::User;
...
sub validate_user {
my $self = shift;
my $q = $self->query;
my $user = MyApp::Db::User->search(
username => $q->param('username '),
password => $q->param('password')
);
if($user) {
# laugh
}
else {
# cry
}
}
| [reply] [d/l] [select] |
|
Already using sort of abstraction. It's a module that I'm working on - mentioned it on this node > 487823.
Anyway correct me if I'm wrong, but in case I wouldn't pass this object "around", every time I need DB access, new connection would be made?
I mean, CGI::Session would connect on itself, then once again my code to check the data and add user? Who knows how many connections would be made latter on when I add all sorts of things ... Obvisoly I'm using plain CGI, Apache::DBI (or whichever) is not available ...
| [reply] |
|
No, Class::DBI is built on top of Ima::DBI which does database connection caching, so you wouldn't be re-connecting each time you use the Class::DBI objects.
| [reply] |
Re: OOP design related question.
by dragonchild (Archbishop) on Sep 09, 2005 at 16:42 UTC
|
That's generally how these things are done. Sounds like you have the right idea - give each object exactly what it needs to do its job.
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?
| [reply] |
|
Well as you can probably notice - I'm trying to get better in OOP so I was wondering if there is some better way ...
| [reply] |
Re: OOP design related question.
by saberworks (Curate) on Sep 09, 2005 at 18:29 UTC
|
I like having a CGI::App accessor method, similar to $self->query(), like:
my $dbh = $self->dbh();
I've also seen a module created just for that, so you say:
use MyDBH;
my $dbh = MyDBH->get();
# or Exporter, but I hate this.
use MyDBH qw($dbh);
or something. In my opinion the first option is cleaner and more consistent. It seems silly to always say $self->param('dbh'); when you can just define your own dbh() method. More consistent, too (since it is required by your app to function). | [reply] [d/l] [select] |
Re: OOP design related question.
by perrin (Chancellor) on Sep 09, 2005 at 18:52 UTC
|
It depends on the scope of your object. Your idea is okay if it only lasts for the one request. If it lasts longer, that DBI connection might get replaced by a new one. I typically write an accessor method for getting a database handle instead. Some people use a singleton object, which is pretty much the same deal. | [reply] |
|
Yes it only last for one request as I'm using CGI. If I were using mod_perl I believe there is a module that would take care of this, and it would overwrite (dis)connect calls. So I could call it 10 times - and no new connection would be made?
| [reply] |
|
You're thinking of Apache::DBI. However, if you keep the connection open long enough, the server will break it. That will result in Apache::DBI reconnecting, and that would mean any copies of the old $dbh you have sitting around will not work.
| [reply] |
Re: OOP design related question.
by InfiniteLoop (Hermit) on Sep 09, 2005 at 19:09 UTC
|
As I understand you want to make db related calls from your Controller. Why don't you encapsulate the DB calls in a separate perl module ? You could have this:
- YourApp::CGI::User - the controller
- YourApp::USer - the model class
Your controller would validate the data (via Data::FormValidator) and invoke methods in the model class to check the user/session in the db. The the db handler could reside in the model class.
| [reply] |
|
MCV - actually that might be the reason why I posted this question. I was thinking if there is some way things like this are done. As I'm not too familiar with MCV so I wouldn't know?
This would also answer questions of few others...
As I use sessions to see who's logged in, usually connection is made (in cgiapp_init sub of main module which is called before anything else is executed) on start-up. So I'm not sure if what you are proposing is possible? Anyway the disconnect is called by destructor of my module (DBIx::Handy)...
| [reply] |
Re: OOP design related question.
by Ctrl-z (Friar) on Sep 10, 2005 at 10:58 UTC
|
I would second the Singleton approach suggested by others - the dbh isn't really an attribute of your objects, your just stashing it to pass around. You might as well provide a global point of access for objects if and when they need it.
It would also give you a logical place to put other generic DB related code, and could be quite easily integrated with your DBIx::Handy module:
package DBIx::Handy;
my $dbh = undef;
sub get_instance
{
my $class = shift;
if($dbh == undef){
$dbh = ...
}
return $dbh;
}
...etc
time was, I could move my arms like a bird and...
| [reply] [d/l] |
|
Don't know if you noticed but I'm not actually passing DBH. I'm passing reference to instance of DBIx::Handy actually.
The thing that I dont understand about singleton's. I know that you can create only one at any given time - but is that sort to say system wide - or just in that process?
I believe it's just for that process of course - but then again it raises questions what happens when you work under mod_perl or similar (threads?)
Anyway the code that you wrote (just checking if I understood everything right) allows me to create more instances of DBIx::Handy, but only one DB connection?
Problem is that I will need two DB connections as at one point of time, I need to read data from another DB ... One idea would be to use Memoize and since call to connect would have different parameters it would create different DBH. Then it gets even more complicated :) As to do so I need to store several DBH and somehow tell to DBIx::Handy which one to use for particular query... But I like your idea as it's simple...
| [reply] |
|
Anyway the code that you wrote (just checking if I understood everything right) allows me to create more instances of DBIx::Handy, but only one DB connection?
If you want that, then $dbh is just the equivalent of a static field, shared between all DBIx::Handy objects (in a process). That may be fine, but to be a classic Singleton get_instance should conditionally create and return the sole instance of its class.
The difference is more obvious in Object Oriented languages where new is more than just a naming convention. See SingletonPattern.
The nice thing about design patterns is that they should be modified to suit your needs. In your case, I would be inclinded to have a class that manages the configuration and singleton-ness of your database handle(s). That way you avoid passing around existing objects or creating new ones that just wrap static fields. By encapsulating each database connection in a class, you can skip the need for memoization and localize changes. Bonus!
package DBIx::Handy::Singleton;
our @ISA = ('DBIx::Handy');
my %instances = ();
sub new{
die "ack! Cant call new on Singleton class '$_[0]'!\n";
}
sub get_instance
{
my $class = ref $_[0] || $_[0];
unless($instances{$class}){
my $dbh = bless DBIx::Handy->new( $class->configure() ), $clas
+s;
$dbh->connect();
# ...etc
$instances{$class} = $dbh;
}
return $instances{$class};
}
sub configure{
die "Unimplemented abstract method 'configure' in '$_[0]'\n";
}
package MyApp::Foo;
our @ISA = ('DBIx::Handy::Singleton');
sub configure{
return (driver => ..., database => ..., ...);
}
package MyApp::Bar;
our @ISA = ('DBIx::Handy::Singleton');
sub configure{
return (driver => ..., database => ..., ...);
}
As for the mod_perl/threading issues, I dont know enough about either to comment fully, other than to say that usually access
to get_instance would be limited to one thread to avoid race conditions while checking the singleton exists. Check CPAN,
a quick search brought up Apache::Singleton...
time was, I could move my arms like a bird and...
| [reply] [d/l] |
|
|
Don't know if you noticed but I'm not actually passing DBH. I'm passing reference to instance of DBIx::Handy actually.
The thing that I dont understand about singleton's. I know that you can create only one at any given time - but is that sort to say system wide - or just in that process?
I believe it's just for that process of course - but then again it raises questions what happens when you work under mod_perl or similar (threads?)
Anyway the code that you wrote (just checking if I understood everything right) allows me to create more instances of DBIx::Handy, but only one DB connection?
Problem is that I will need two DB connections as at one point of time, I need to read data from another DB ... One idea would be to use Memoize and since call to connect would have different parameters it would create different DBH. Then it gets even more complicated :) As to do so I need to store several DBH and somehow tell to DBIx::Handy which one to use for particular query... I like your idea as it's simple...
| [reply] |
Re: OOP design related question.
by skx (Parson) on Sep 10, 2005 at 08:59 UTC
|
One thing I've done in a similar setup is to create a singleton object to hold the database handle.
The first time it is used it connects, every other time it returns the previously opened handle.
This allows me to use the database handle wherever it is needed and not explicitly pass it around.
| [reply] |