Your skill will accomplish what the force of many cannot |
|
PerlMonks |
The initial release of the code had a possible bug related to the order that the Catalyst instances get loaded. The risk being that the HomeGrownModel glue class could load before the DBIx::Class one. The new version delays the initialization of the HomeGrownModel class instance, until after the application has fully loaded by means of using the Moose after feature on the Catalyst method setup_components. The code explanations below were adjusted to reflect this.
There was still a bug on modelthree that needed fixing. That's what happens when you don't write tests, so I will be creating these tests soon.
A lot of information is available about models, both in the Catalyst POD, books, this Wiki and the many discussion on the topic in mailing lists forums and IRC. Everywhere you read you will find comments like "make your controllers thin", "keep your model independent from Catalyst", "the model is just a thin layer", etc. but none of them actually tell you HOW to do it.
This guide was developed with actual code to help you understand quickly and easily the different "Model" options you have at your hands when developing with Catalyst. in the best Perl tradition, TMTOWTDI so you are invited to continue your study after going through this guide.
By now you should have a pretty good idea of the MVC design pattern, so to correctly separate the concerns, your business logic should be written as generic re-usable code that is independent from Catalyst itself. So the first distinction we must make is the difference between YOUR Model and the Catalyst Model Layer. Like everyone agrees: your model should be implementation-independent code that can be re-used, and hopefully published on the CPAN for anyone to use, with or without Catalyst, and the Catalyst Model should be a thin wrapper that connects your model with the catalyst M layer.
The biggest confusion comes from the fact that both in Catalyst and almost every other MVC Web development framework, the M layer is analogous to the ORM (DBIx::Class in Catalyst's case).
This makes some sense because after all, in many Web applications, the M code is very much CRUD with some simple logic. But models can, and should be much more than that...
svn co svn://svn.yabarana.com/catalyst/ModelsThis will download a very simple Catalyst application that demonstrates the three model options described in this guide.
Pretty straight forward, except that it's very deceiving for the inadvertent coder. Many people don't realize that the line:sub index :Path :Args(0) { my ( $self, $c ) = @_; my @people = $c->model('Models::People')->all(); $c->stash->{people} = \@people; }
$c->model('Models::People')->all()is actually a shortcut for this:
$c->model('Models')->schema->resultset('People');Remember that "Models" is the name of the Catalyst application developed for this guide.
sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->stash->{people} = $c->model('Models::People')->listall(); }
Here the extended method "listall" hides away any complexity inside the extended class which is simply a common DBIC overlay class like so:
For many applications this way of coding your models will be more than adequate, but only if the business code is closely related to the RDBMS model. In this pattern, you will probably hang your business method to the closest DBIC class and from there you might wind up using other DBIC classes (i.e. a method in one class that updates several tables). To accomplish this you will need a handle to the DBIC schema itself and the next pattern will show you how.package Models::Schema::ModelsDB::ResultSet::People; use strict; use warnings; use base 'DBIx::Class::ResultSet'; sub listall { my $self = shift; my @people = $self->all(); return \@people; } 1;
This example demonstrates two things. Firstly, is the Catalyst Model wrapper which acts as an interface between your homegrown independent Perl class and the Catalyst framework:sub index :Path :Args(0) { my ( $self, $c ) = @_; $c->stash->{people} = $c->model('HomeGrownModel')->homegrown_peop +le(); }
package Models::Model::HomeGrownModel; use base 'Catalyst::Model'; use HomeGrown; use Moose; our $AUTOLOAD; has 'HomeGrownInstance' => ( is => 'rw', isa => 'HomeGrown', ); # Gotcha: using the COMPONENT method like shown below may get you in # trouble because your component mat very well be loaded before the # DBIC model does. The initialize_after_setup hack shown further down, # is a work-around to deferr the instatiation of things, or at least # making sure that your model initializes lastly. #sub COMPONENT { # my ( $self, $app ) = @_; # my $dbic_schema = $app->model('ModelsDB')->schema; # return HomeGrown->new( schema => $dbic_schema ); #} sub initialize_after_setup { my ( $self, $app ) = @_; $app->log->debug('Initializing Homegrown with schema AFTER app is fu +lly loaded...'); $self->HomeGrownInstance( HomeGrown->new( schema => $app->model('ModelsDB')->schema ) ); } # Here you would map your Catalyst Model methods to the actual model # in this example we will just forward everything as is to our # HomeGrown external Model sub AUTOLOAD { my $self = shift; my $name = $AUTOLOAD; $name =~ s/.*://; $self->HomeGrownInstance->$name(@_); } 1;
As you can see this is just a very thin wrapper around your homegrown class. We also demonstrate a correct way (YMMV) of passing the instantiated DBIC schema to your homegrown external class so you can access the database from there whilst taking advantage of all the connection and re-connection magic of DBIC which is already set-up and working for you thanks to Catalyst.
Finally, for this to work correctly, your model class should be instantiated lastly by Catalyst. This bit of Moose after magic in the main application class (Models.pm) will do the trick. This code must be placed before the application configuration setup sections, preferably right after the $VERSION declarations. See actual sample code for details.
after 'setup_components' => sub { my $app = shift; for (keys %{ $app->components }) { $app->components->{$_}->initialize_after_setup($app) if $app->components->{$_}->can('initialize_after_setup'); } };
How this hack works: The after feature of this Moose declaration will call the inline sub after the setup_components sub of the main application class has been called, that is, after all other components have been set up. The loop in this sample sub will go through every loaded component and identify those that have the initialize_after_setup method and call it if it exists.
Your Model class would look something like this:
Notice how your model (or more precisely your business logic implementation) class is completely independent from Catalyst and can be used for any purpose. In our example above, it still depends on a specific database schema and of course DBIC but it's just for illustration of the concept. another approach could be to do the DBIC stuff in the Catalyst model layer (the thin layer above) and only pass specific data to the actual implementation class, this way keeping the latter independent even from that particular database model. As always TMTOWTDI and your mileage may vary, but hopefully this guide has served you as an opening these possibilities and variants on the same idea: keep your logic code in the M layer and the Catalyst M layer should be as thin as possible.package HomeGrown; use Moose; use namespace::autoclean; has 'schema' => ( is => 'rw', required => 1, isa => 'DBIx::Class::Schema', ); sub homegrown_people { my $self = shift; my @people = $self->schema->resultset('People')->all(); return \@people; } 1;
|
---|
Replies are listed 'Best First'. | |
---|---|
Re: Catalyst Models Definitive Guide
by mkchris (Sexton) on Jan 20, 2022 at 11:27 UTC | |
by Your Mother (Archbishop) on Jan 20, 2022 at 12:46 UTC | |
by mkchris (Sexton) on Jan 21, 2022 at 12:03 UTC | |
by syphilis (Archbishop) on Jan 20, 2022 at 12:49 UTC | |
by mkchris (Sexton) on Jan 21, 2022 at 12:04 UTC | |
by yewtc (Acolyte) on Jan 20, 2022 at 15:36 UTC | |
by mkchris (Sexton) on Jan 21, 2022 at 12:10 UTC | |
by ait (Hermit) on Dec 01, 2022 at 09:58 UTC |