in reply to Re^4: Trying to understand Catalyst::Model::Adaptor
in thread Trying to understand Catalyst::Model::Adaptor

Oh, sure. There is a way to do that which buys you, in general, the best of both worlds.

Something I do in most apps is keep parallel directories for agnostic parts of the application. The DB schema for example. I almost always use DBIx::Class. I put it parallel to the MVC parts. E.g.,

MyApp::Controller MyApp::Model MyApp::View MyApp::Schema (or MyApp::DBIC or whatever) MyApp::SomeSpecialTextTransforms MyApp::ExternalWebservice

Then only the MVC are directly tied to the web app and the other parts are generic modules which can be used without the app context ($c).

I also generally keep a MyApp/bin for scripts I write that are not directly related to the web app and put scripts that are in the MyApp/scripts with the usual suspects.

The extra classes are hooked into your web app through Model glue. Some are all but done for you, like Catalyst::Model::DBIC::Schema. Others you typically ride in on Catalyst::Model::Adaptor like we were discussing. This way the customizations you make to things are available to all your code in the most natural way and you only have to (in the best case) ever edit code in one place to get the changes to propagate everywhere you want. The single drawback to this is the Catalyst configuration system is too tightly bound to the application. There have been discussions about fixing this but no motion so far.

Good luck and have fun!

(update: spelling fixes.)

Replies are listed 'Best First'.
Re^6: Trying to understand Catalyst::Model::Adaptor
by tospo (Hermit) on Apr 07, 2010 at 20:54 UTC

    Thanks. I agree to everything you say but I'm still not 100% sure about the benefits of Catalyst::Model::Adaptor. Let's assume a very simplistic scenario where I have the following model class that I want to use in my web app:

    package NonCatalyst::FileCat; sub new{ my ($class, $directory) = @_; bless { _directory = $directory }, $class; } sub cat{ my ($self, $file) = @_; my $cmd = 'cat '.$self->{_directory}.'/'.$file; return `$cmd`; } 1;
    So I instantiate with a directory and now I can get the contents of text files with the cat method on a file. Not very useful and realistic but it's just an example. Now I want my web app to display the contents of a file the user selects in a form. My instinct would be to do it like this in my controller:
    use NonCatalyst::FileCat; sub show :Local { my ( $self, $c ) = @_; my $fc = NonCatalyst::FileCat->new( $c->config->{file_directory}); my $file_name = $c->request->params->{file}; $c->stash->{result} = $fc->cat( $file_name ) ; }

    So I just use the external model class directly. If I need to make a change to FileCat I still only have one file to edit, so no difference to the Model::Adaptor approach there. All the configuration I need for FileCat is passd explicitly when creating the object, so I can use the same module from any script or UI implementation as long as it can pass a directory path. To me, that still seems a lot more natural and I would avoid the drawback you mention, i.e. tying my class too closely to the Catalyst configuration. Maybe this example is too simple but would you agree that this would be a case against using Catalyst::Model::Adaptor?

      Me personally? Nope :) Generally the only case I'd agree it would be best to skip the model is the case where the code will only ever be used once in a single Controller. Forever and ever. I could see that sort of functionality being in several Controllers and methods (though if you do write something like that use Path::Class::File->new(path)->slurp or something similar instead of invoking shell commands). These lines-

      use NonCatalyst::FileCat; my $fc = NonCatalyst::FileCat->new($c->config->{file_directory}); my $file_name = $c->request->params->{file}; $c->stash->{result} = $fc->cat($file_name);

      -become-

      $c->stash->{result} = $c->model("FileCat")->cat($c->request->params- +>{file});

      Granted you have cost yourself up front with setting up the adaptor and configuration for the model. But it really is trivial effort and it allows you to change the file_directory in config related to the functionality -- Model::FileCat -> file_directory: /some/place -- instead of in the "global" app config. Theoretical win but better namespacing nevertheless.

      I'm not trying to preach; thinking in MVC instead of scriptese is a really good way to save yourself misery over time. I wish someone had hit me with this particular cluebat much earlier. Your own use case and preference matters, of course. Do what thou wilt.

        OKOK, forget the actual file reading - it was just an example of a simple model that needs a little bit of configuration (the directory) and a parameter to spit out some result.

        I still don't quite get what's wrong with using this in several controllers. the model is completely separated from the controller and if I need to make a change to the way I read files (i.e. use something more sensible than backticks in this case...), I still only change the model, i.e. my NonCatalyst::FileCat module in a single place. How would the adaptor approach help me with that?

        It seems to me that the only benefit of using an Adaptor is that the model has access to the config. I can see the point for that if the model needs a lot of configuration and is tightly tied in with the Catalyst App but if it simply requires a directory to work with or some other trivial data, then it seems that I get much better separation of web app implementation and back-end model by using the NonCatalyst class directly. The way I suggested, I can just as easily use the model class from a command line script or a desktop GUI app or something.

        Apologies for being so persistent :-) and thanks a lot for your patience. It's just that this seems so counterintuitive to me and I can imagine that others have the same questions. I like Catalyst so far but there is not as much information out there as for some other web frameworks.