#1
  1. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2009
    Posts
    334
    Rep Power
    246

    Catalyst -> Access adapted model from a model


    Hi,

    I seem to be going round in circles trying to understand how I can access my adapted class (Catalyst::Model::Adaptor) from within a normal Catalyst model.

    I have a database class (using DBI) called 'Sql', which I have now wrapped up in an adapted model 'Members'. - so far so good

    I then have my actual model called 'Login', that needs to use this adapted model class .

    In my 'Login' model it needs access to 'Members', which is set as an object attribute along with additional application context ($c) data for session and IP.

    Code:
    package Members::Model::Login;
    use Moose;
    use namespace::autoclean;
    extends 'Catalyst::Model';
    
    # Attributes
    has 'Members' => (
        is          => 'ro',
        isa         => 'Object'
        );
    
    has 'IP' => (
        is          => 'ro',
        isa         => 'Str'    
        );    
      
    
    has 'Session' => (
        is          => 'ro',
        isa         => 'HashRef'  
        );
    
    has 'SessionID' => (
        is          => 'ro',
        isa         => 'Str'
        );
    I would like to then in my controller use the 'Login' model.

    I have found to make this work, my controller has to access it via...

    Code:
    $c->model('Login')->new( 
                        Members     =>  $c->model('Members'),
                        IP          =>  $c->req->address,
                        Session     =>  $c->session,
                        SessionID   =>  $c->sessionid
                        )->LogCheck();
    Is this the only way to give my 'Login' model access to my 'Members' model?

    I would prefer Catalyst to set up the attribute 'Members' itself when instantiating the 'Login' model so I don't keep having to pass it in the constructor.

    Is this also possible for the other attributes?

    If so how would I do this? So I could simply then use
    Code:
    $c->model('Login')->LogCheck();
    Though I assume I have to pass in $c->session etc. each time otherwise all requests would use the same details, but it would be good if I could get 'Members' auto loaded, as it never changes so the one instantiation of it is fine.

    After all Catalyst has already instantiated 'Members', so I just want access to this object from the 'Login' model.

    Though as I'm using the 'new' constructor on my 'Login' model, am I getting a newly created model object each time anyway and not the Catalyst auto instantiated one?

    I'd also like to make the 'Members' attribute set to 'required => 1', but currently the app falls over because when Catalyst auto instantiates 'Login', it isn't passing in the required attribute 'Members'

    I have read that you can add the Catalyst context to your model as an alternative option via
    Code:
      
      
    
    __PACKAGE__->mk_accessors(qw|context|); 
        sub ACCEPT_CONTEXT {
            my ($self, $c, @args) = @_;
            $self->context($c);
            return $self;
        }
    So in my 'Login' model I can then use
    Code:
    $self->context->model('Members')->my_method
    instead of having 'Members' as an attribute of the 'Login' model.

    But I've also read to steer well clear of this and not to do it, as it binds your app to Catalyst and is just as bad as passing in $c

    Plus it's pretty horrible syntax to have to use anyway, when my current solution is simply
    Code:
    $self->Members->my_method
    Which is at least keeping my model unbound from Catalyst and only loosley coupled to the data types it needs to function on its own.

    So help understanding all this really is appreciated.

    Regards,
    1DMF
    Last edited by 1DMF; November 15th, 2012 at 03:28 PM.
    Free MP3 Dance Music Downloads

    To err is human; To really balls things up you need Microsoft!
  2. #2
  3. No Profile Picture
    Contributing User
    Devshed Novice (500 - 999 posts)

    Join Date
    May 2007
    Posts
    765
    Rep Power
    928
    Catalyst calls a couple methods (new; COMPONENT) during construction (see Catalyst::Component). If you order things right in your application module you may be able to grab references to the other models at that point.

    Alternatively, you could have the other model passed in as a parameter and then create a convenience method in your application module to keep your controller code clean. Something like:

    Code:
    # In MyApp::Controller::Whatever
    sub someAction {
       # ...
       $c->loginCheck();
       # ...
    }
    
    # In Myapp
    sub loginCheck {
       $c->model('Login')->loginCheck( $c->model('Members'), $c->sessionId, ... );
    }
    
    # In MyApp::Model::Login
    sub loginCheck {
       my ($members,$session,...) = @_;
       # ... 
    }
    Though I wonder if loginCheck(ip,session,...) should be a method of the 'Members' model itself. From what you've shown, the Login model just stores/manipulates the data in Members. Unless there's more to it, I'd take that as hint that Login really isn't a model at all.
    sub{*{$::{$_}}{CODE}==$_[0]&& print for(%:: )}->(\&Meh);
  4. #3
  5. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2009
    Posts
    334
    Rep Power
    246
    I tell you what the problem is, Catalyst models aren't models!

    I've been going round in circles, keep being told put you code in the model, put your code in the model.

    So I have, but then a nice chap from the Catalyst group kept saying I wasn't doing it right and I need to decouple it from Catalyst.

    Finally last night the penny dropped, I've been putting my business logic in the wrong place, because Catalyst are wrongly calling their 'Wrapper' a 'Model' when it isnt a Model as no logic code goes in it!

    Now I need to refactor my code to a stand alone class and work out how to wrap it in the Adaptor and pass it in the context vars I need!

    As much as I am enjoying learning a new framework and loving it being in perl, the Catalyst Docs are shockingly bad, full of inaccuracies, bad teminology and total double dutch at times.

    So for anyone else out there starting out with Catalyst, if you are being told to put your code in the Model, that does not mean 'Catalyst::Model' , because they are not Models!

    Catalyst like to call it 'Glue' - 'Not a way of life', but instead of making up strap lines, they should spend more time on proper terminology and documentation!

    Still haven't worked out how to wrap my class up, but at least I have my code in the right place now!

    Regards,
    1DMF
    Last edited by 1DMF; November 17th, 2012 at 06:03 AM.
    Free MP3 Dance Music Downloads

    To err is human; To really balls things up you need Microsoft!
  6. #4
  7. Contributing User
    Devshed Newbie (0 - 499 posts)

    Join Date
    Dec 2009
    Posts
    334
    Rep Power
    246
    Just a heads up for others.

    After refactoring out to a REAL model class, I needed to pass it some application vars as well as another model.

    Contrary to the documentation for Catalyst::Model::Adaptor
    prepare_arguments
    This method is passed the entire configuration for the class and the Catalyst application, and returns the hashref of arguments to be passed to the constructor. If you need to get dynamic data out of your application to pass to the consturctor, do it here.

    By default, this method returns the args configuration key.

    Example:

    sub prepare_arguments {
    my ($self, $app) = @_; # $app sometimes written as $c
    return { foobar => $app->config->{foobar}, baz => $self->{baz} };
    }
    The method 'prepare_arguments' DOES NOT get passed the application context variable normaly written as $c (the context of the request).

    It is simply passed $app which is nothing more than the string name of the application class.

    If you need $c, you have to use Catalyst::Model::Factory / Catalyst::Model::Factory::PerRequest , this way the Catalyst wrapper acts as a factory for instantiating your model objects giving you the opportunity to pass in required attributes to your model's constructor...

    E.G.
    Code:
    package MyCatalystApp::Model::MyCatalystModel;
    use strict;
    use warnings;
    use base 'Catalyst::Model::Factory::PerRequest';
       
    __PACKAGE__->config(
        class           => 'MyRealModelClass',
    );
    
    sub prepare_arguments {
        
        my ($self,$c) = @_; 
        return {
              AnotherCatalystModel   =>  $c->model('AnotherCatalystModel'),
              IP          =>  $c->req->address,
              Session     =>  $c->session,
              SessionID   =>  $c->sessionid          
        };
            
    }  
          
    1;
    Rememer to place your real model classes in the 'lib' folder and the Catalyst model wrappers in the 'Model' folder.

    Hope it helps others!
    Last edited by 1DMF; November 19th, 2012 at 08:11 AM.
    Free MP3 Dance Music Downloads

    To err is human; To really balls things up you need Microsoft!

IMN logo majestic logo threadwatch logo seochat tools logo