[Catalyst] Re: Using model layers between Catalyst and DBIC

Jason Galea lists at eightdegrees.com.au
Tue Feb 7 14:38:21 GMT 2012

On Tue, Feb 7, 2012 at 1:26 PM, Bill Moseley <moseley at hank.org> wrote:

> My idea was that Catalyst would call a method in the new model layer and
> possibly get a DBIC object back.  There is concern from some at my meeting
> that we don't want to give the Catalyst app developer a "raw" DBIC object
> and that we should wrap it (as it appears you are doing, Jason) in yet
> another object.   That is, we want to allow $user->first_name, but not
> $user->search_related or $user->delete.
> That requires writing new wrapper classes for every possible result -- not
> just mirroring DBIC's result classes but possibly many more because the n=
> model might have multiple calls (with different access levels) for fetchi=
> user data.  That is, $user->email might work for some model methods that
> return a user but not methods called on the model.
> Frankly, to me this seems like a lot of code and work and complexity just
> to prevent another developer from doing something stupid -- which we cann=
> prevent anyway.  And smart programmers can get at whatever they want,
> regardless.  Seems more risky to make the code more complex and thus hard=
> to understand.  The cost/benefit ratio just doesn't seem that great.
> Am I missing something?

nope.. the complexity involved continues to reveal itself to me..

- account for the fact that sometimes a DBIC relation gives us our
instance object and sometimes a dbic result object. (to do with how it got

     my ($self) =3D @_;


     my $user =3D $self->_record->user;


     my $user_class =3D $self->user_instance_class;

29 **

-    return $user ?

30 **

-        $user_class->new('_record' =3D> $user )

31 **

-        : $user_class->new();


+    $user =3D $user_class->new('_record' =3D> $user ) if $user &&


+    return $user || $user_class->new();





> I suppose this is not unlike the many discussions about what to pass to
> the view.  Does the controller, for example, fetch a user object and pull
> the data required for the view into a hash and then pass that to the view?
>  Or does the controller just fetch a user object and pass that directly to
> the view to decide what needs to display?
> I prefer just passing the object to the view.  The controller code is much
> cleaner and then when the view needs to change don't need to also change
> the controller.  And when there's a different view (like an API or moble )
> the same controller action can be used.
> Thanks,
>> In addition to everything already mentioned I wanted to get Bread::Board
>> in on the act..
>> I've put Lecstor up on GitHub if you're interested along with a Catalyst
>> app that uses it. Neither really do much but boy is there a lot of
>> scaffolding! ..and all the tests pass! 8)
>> The basic hook-up is Catalyst -> Catalyst Models -> Bread::Board
>> Containers -> Lecstor App/Models -> DBIC and others.. decoupled like a
>> broken bag o marbles..
>> the Catalyst Models:
>>  - LecstorApp - application lifetime
>>  - LecstorModel - application lifetime
>>  - LecstorRequest - request lifetime
>>  - Lecstor - request lifetime
>> LecstorModel & LecstorRequest return Bread::Board containers.
>> LecstorApp returns a parameterized Bread::Board container
>> Lecstor grabs the first two and shoves them into the third to make
>> another Bread::Board container from which I get my app..
>> have I gone walkabout!?
>> https://github.com/lecstor/Lecstor
>> https://github.com/lecstor/Lecstor-Shop-Catalyst
>> comments welcome.
>> cheers,
>> J
>> On Tue, Jan 10, 2012 at 12:16 AM, Jason Galea <lists at eightdegrees.com.au=
>>> On Mon, Jan 9, 2012 at 3:14 PM, Bill Moseley <moseley at hank.org> wrote:
>>>> On Monday, January 2, 2012, Jason Galea wrote:
>>>>> I think I've added another layer but I'm not sure where you draw the
>>>>> line.. I have a model layer over DBIC pulling together related result
>>>>> classes under a single model class. Then the app? layer uses the model
>>>>> layer to get things done. So I'd probably have one "distribution" tha=
t is
>>>>> our DBIC wrapped in a model layer layer and use that in a number of a=
>>>>> 8) Each app can then be used as the single model in a Catalyst app or
>>>>> script or whatever.. (I think I need more names for the parts..)
>>>> Yes, where to draw the line is difficult to know.   I've only had a few
>>>> hours to work on this but already I feel like I'm reinventing Catalyst=
>>>> mostly because my model layer is pulling in much of the components tha=
t my
>>>> Catalyst app would normally do -- DBIC, caching, even some concept of =
>>>> "current user".   Access control is another topic.
>>> The problem parts for me are DBIC and TT. I thought I could just set up
>>> the components as usual, then load my app with them but it get's tricky
>>> calling one component from another at setup time, although it all works
>>> fine if you instantiate the app per request. So now I'm connecting/crea=
>>> those myself.
>>> For other things provided by plugins I'm working more with Catalyst so
>>> for caching I will probably have my app accept a cache object at
>>> construction and pass in the Catalyst cache. For Authentication I've
>>> created my own store and user for the Catalyst Authentication plugin and
>>> they use my app to do what they have to. I've also created a store for =
>>> session plugin which uses my app, so all-in-all my app can see/touch
>>> everything that Catalyst is doing, and I can still make use of all the
>>> Catalyst stuff available (hopefully).
>>>>> I have "Sets" in lu of ResultSets and "Models" for Results. Although
>>>>> in most instances a Model will actually cover the usage of multiple
>>>>> Results. Each Set gets the dbic schema object and knows it's resultset
>>>>> name. Each model has a data attribute which contains a dbic row objec=
t and
>>>>> "handles" any methods I don't need to override via the Moose "handles"
>>>>> attribute attribute!?
>>>>> Set->create($hash) creates the dbic object and stuffs it into a model
>>>>> class and returns that.
>>>> So you are mirroring DBICs class structure a bit.  I need to consider
>>>> that approach more as currently my model layer returns the DBIC row ob=
>>>> directly.  So, I have something like this:
>>>> my $user_model =3D Model::User->new;
>>>> my $new_user =3D $user->new_user( $user_data );
>>>> Not as flexible as your approach but my goal currently is to just
>>>> abstract out the ORM so that Model::User can hide the specifics of the
>>>> database.   Actually, it's not that hard to do directly with DBIC, eit=
>>> yeh, I decided a while back that DBIx::Class is complicated enough and
>>> I'm too lazy to keep trying to work out complicated solutions in the DB=
>>> classes to do things I know I can do quickly and easily with regular Mo=
>>> classes.. and I like having nice clean DBIC classes..
>>>>> Each result class that has a model class overrides it's inflate_result
>>>>> method which again stuffs the dbic row object into the model object so
>>>>> searches on the related dbic resultsets return my model objects.
>>>> Can you show a code example of that?  I'm not sure I'm following why
>>>> you use that approach instead of having your layer on top of DBIC do t=
>>>  and the exception to the rule.. I did have my Set classes (which I now
>>> refer to as Model Controllers) grabbing search results and looping thro=
>>> inflating them all into my Model Instances but then I couldn't just gra=
b a
>>> resultset if I needed to limit/restrict/whatever, and any search or find
>>> had to be put through that ringer. With inflate_result I know that no
>>> matter how I get the results they'll be instances of my model. create is
>>> the only thing that doesn't work for so my controller does the wrapping
>>> there.
>>> package Lecstor::Schema::Result::Person;
>>> use base qw/DBIx::Class/;
>>> __PACKAGE__->load_components(qw/ Core /);
>>> __PACKAGE__->table('person');
>>> __PACKAGE__->add_columns('id' ,'firstname','surname');
>>> __PACKAGE__->set_primary_key('id');
>>> sub inflate_result {
>>>     my $self =3D shift;
>>>     my $ret =3D $self->next::method(@_);
>>>     return unless $ret;
>>>     return Lecstor::Model::Instance::Person->new( _record =3D> $ret );
>>> }
>>> 1;
>>>>> Each model class has a validation class based on.. Validation::Class
>>>>> and create & update run their input through that. If there are errors=
>>>>> stuff the errors into a very basic exception object and return that. =
>>>>> way I can return the same exception object no matter where the error =
>>>>> from, eg a dbic exception..
>>>> Yes, I'm doing something very similar where validation happens before
>>>> the method in the model and on validation errors and exception is thro=
>>>> (if you are on the Moose list you may have seen my example).
>>>> Thanks for the feedback and the ideas,
>>> no worries at all, happy to be able to provide it.
>>> cheers,
>>> J
>>>> --
>>>> Bill Moseley
>>>> moseley at hank.org
>>>> _______________________________________________
>>>> List: Catalyst at lists.scsys.co.uk
>>>> Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
>>>> Searchable archive:
>>>> http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
>>>> Dev site: http://dev.catalyst.perl.org/
>> _______________________________________________
>> List: Catalyst at lists.scsys.co.uk
>> Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
>> Searchable archive:
>> http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
>> Dev site: http://dev.catalyst.perl.org/
> --
> Bill Moseley
> moseley at hank.org
> _______________________________________________
> List: Catalyst at lists.scsys.co.uk
> Listinfo: http://lists.scsys.co.uk/cgi-bin/mailman/listinfo/catalyst
> Searchable archive:
> http://www.mail-archive.com/catalyst@lists.scsys.co.uk/
> Dev site: http://dev.catalyst.perl.org/
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20120208/fbe12=

More information about the Catalyst mailing list