Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

How to organize Catalyst stash

by roman (Monk)
on Nov 18, 2010 at 10:25 UTC ( #872182=perlquestion: print w/replies, xml ) Need Help??

roman has asked for the wisdom of the Perl Monks concerning the following question:

I wonder how the fellowmonks pass their model objects into templates. Do you pass directly the objects or do you expand them into individual properties (pass them as hashrefs)?

If you use the first method (objects) how do you pass the additional information for the object?

Let us say we need to display a list of some kind of payments (DBIx::Class objects). It could be comfortable just:

$c->stash(payments => \@payments);

But there are other information we need to supply with each payment:

  • URLs (for payment detail, payment cancellation)
  • textual descriptions for foreign keys values ( payment state, payment method, ...)
  • other payment properties not contained in the payment object. For example whether payment is due (would be displayed in red).

So would you pass the list like this:

$c->stash->{payments} = [ map { { is_due => $this->is_due( $c, $_ ), cancel_url => $c->uri_for_action( '/payment/cancel', [ $_- +>id ] ), detail_url => $c->uri_for_action( '/payment/view', [ $_- +>id ] ), payment_method => $this->payment_method_descr_for( $c, $_->payment_metho +d_id ), # how about the original object ? payment => $_, # or payment_id => $_->id, payment_quantity => $_->quantity, # .... } } @payments ];
or would you pass just the payments and compute the additional info (and URLs) in template (It doesn't seem very MVCish to me)?

Finally how do you pass dates? As objects or formatted already?

Any comments are really appreciated.

Replies are listed 'Best First'.
Re: How to organize Catalyst stash
by sundialsvc4 (Abbot) on Nov 18, 2010 at 14:05 UTC

    As a general rule, I think that an object should be entirely the master of its own affairs ... including “the set of properties that it may contain,” and “the means to present information about itself, in whatever format may be needed by anyone.”   You don’t want to have a “third-party” piece of code that is dependent on knowing what an object is supposed to contain, nor that acts as an intermediary between two other parties, because it now becomes functionally dependent upon both of them.

    If a particular object needs to serve-up a date, perhaps formatted in a certain way, then provide at least a method that will return a date-value, and a function (perhaps, which can be applied to any such date) to make it presentable.   If it has a date-property that needs to be assigned, provide an accessor-method for that property which knows how to munch a string, and which croaks (see: Carp) in a meaningful way if that string is no-good.   No one, other than the object itself (or perhaps one of its ancestor classes), knows any of the details – or needs to.   Whatever you need to give the object, and whatever you need to take from it, tell that to the object.

    If you have an object that has “a lot of properties, and you don’t particularly feel like churning out a bunch of accessors for them ... of course you don’t have to.   There is plenty of CPAN support for that sort of thing.   (“Laziness” is a Virtue.)

    If the object needs to serialize itself out to the Catalyst stash, or back in again, once again, there are established tools for doing that.   And the object (or perhaps an ancestor class) provides that service for itself.

Re: How to organize Catalyst stash
by thargas (Deacon) on Nov 18, 2010 at 14:07 UTC

    IMNSHO almost all of those things you call "other information" ought to be part of the payment object, and I would certainly pass that. Textual descriptions should be accessible via the appropriate reference tables using the appropriate methods on your object (specially if it's a DBIx::Class object. The urls can be pulled out of the catalyst context object which your templating system ought to have access to.

    I.E. I'd pass the object. I often stash other stuff, but not for the reasons you mention.

    CENSORED database dates! As far as I'm concerned, there are only three reasonable forms for a date in a perl program:

    • epoch time (seconds since 1970)
    • an ISO date/time string (you know: YYYY-MM-DDTHH:MM:SSZZ) using as much as you need
    • a DateTime object
    Only the first two can be stored in a database. I never use database date formats. I've had to use quite a number of different databases and none of them agree on anything to do with dates/times. And none of them support time-zones. And none of them do what I want. I've had nothing but trouble and wasted time from database date/times. I use an epoch integer or an ISO string, (whichever is appropriate) to store them and convert them to a DateTime object to manipulate them. And it just works. I hate database date/time handling.

    Sorry about that. Now I feel better.

Re: How to organize Catalyst stash
by ruoso (Curate) on Nov 18, 2010 at 16:34 UTC

    First of all, you shouldn't do that.

    You are coupling controller, view and model logic in your controller action. You shouldn't "prepare things for the view" in the controller, but rather just "prepare the context for the view". That meaning, if you're in a inner context (such as one individual customer), the controller should set this up. But NEVER build data structures that are specific for one view.

    You should use more of the TT programming language for such things. In a lot of cases, your controller action will be just empty, because context was defined in an earlier chained action, and you just need to dispatch to one specific view (most of the time, to one specific template).

    That means that you are going to do a lot of "search" calls directyly in your template, but THAT'S OK!!. That's how MVC is supposed to be, remember. It's not a layered schema, it's a triangle.

        M
        ^^
       /  \
      V<---C
    

    Which means, the model is accessed both by the controller and the view, and the controller just dispatches to the view. It is very important that you do not couple the controller to the view, otherwise, implementing an alternative view (RSS, ATOM, email) will be very hard.

    daniel

      In actual practice, as we all know, sometimes you do have to give-way on this design point.   Sometimes, the cleanest way to do something is to have the object produce presentation-ready information about itself.   (If it can do so using templates, so much the better.)

      What I was specifically cautioning against, is the idea of “involving a third-party.”   The problem being, not only that this third-party is wholly dependent upon both of its customers, but also that it is quite easily overlooked.

      I would also say, en passant, that sometimes I have seen exactly this kind of messy dependency ... showing up in ... the templates!   “Templates are ‘code,’ too.”

      There are guidelines here, and rules of thumb, but no rules.   It would be delightful if there were, but these considerations always go very deeply into “the guts of” the application design.   (And, I do intend to have made these comments in accompaniment to, not “in rebuttal to,” yours.   I understand and agree with your points.)

Re: How to organize Catalyst stash (starting SEVCA)
by Rhandom (Curate) on Nov 19, 2010 at 15:42 UTC
    I'm starting a campaign for SEVCA also known as Stop Egregious View Controller Abuse.

    What is SEVCA? SEVCA occurs when the View has Godlike access to the Model and takes it upon itself to act like the Controller. It occurs when developers pass raw unprotected models to the View, and template designers then take over and introduce Controlling business logic into the template. This is typically only possible when using higher power template systems such as Template::Toolkit, Template::Alloy, or HTML::Template::Expr. This problem does not normally result with lower power template systems like HTML::Template or Text::Tmpl.

    What can I do to prevent SEVCA? Stop passing your raw unprotected models to your View. While doing so may allow you to get quick and dirty work done, long term maintenance is hampered by having to figure out if the Controller really is in control or if the View is, or sometimes both are.

    In all seriousness, trying to enforce the boundaries of MVC often ends up as a pedantic debate. In the end, use what works best for you. After 12 years of programming CGIs (short compared to some here I know - and yes this advice applies to Catalyst, CGI::Application, CGI::Ex::App or any other flavor of the month) I have found that what works best for me (notice I said "for me") is to only pass a hashref of needed values to the template - and nearly never ever pass a raw object. Sometimes at the extreme end my hashrefs may contain hundreds of thousands (literally) of values organized into arrayrefs of hashrefs of lists, but more often than not it is a hashref of 0 to 100 values arranged into useful data structures.

    Why keep it simple? Because template designers will use everything you pass, and because it is nice to be able to look at the code and see exactly what template needs so when (not if) you refactor your CGI you know exactly what the template needs without having to read the entire template. (Update: this does open up part of the discussion about where does the view begin. Some say that the lines are drawn at object boundaries, some say that the Template Engine/template are the view, some would argue the boundary begins in a "prepare_template" type of routine, and I'd say that all of the above are correct depending upon the skill/knowledge/needs of the developer, and then I'd say get the job done already because the clock is ticking)

    The best legacy a developer leaves is readable maintainable code.

    my @a=qw(random brilliant braindead); print $a[rand(@a)];

      Note that while I agree that the layered scheme is interesting for a lot of scenarios - this is often called "mediator pattern" - it's a common misunderstanding that MVC implies it.

      In MVC the View must have access to the model objects, and must call the model API directly.

      The cause of that misunderstanding is related to the fact that when using the "mediator pattern", the business logic is implemented in the controller, and the model acts just as a "data access object". But in MVC, the business logic resides in the model, which might, in turn, use "data access objects", such as DBIx::Class, to perform its actions.

      The following diagram explains how working with the "mediator pattern" works.

      
        V          C        M
        I   ---\   T  ---\  O
        E   ---/   R  ---/  D
        W          L        L
      
      

      In this scenario, the controller "encapsulates" the model, implementing the business logic.

      If we were to translate that to MVC, the View in "mediator" is implemented by both the View and the Controller in "mvc", the Controller in "mediator" is the Model in "mvc" and the model in "mediator" is just the "data access object" in "mvc".

      That means: when you work with MVC you probably have a set of Model classes that are not the DBIC objects, implementing the access to them.

      daniel
        Thank you for letting me know about MVC. And for note, my code does not use the Mediator pattern (you've allowed your boundary definitions to infer my usage patterns based on my, probably poor, brief summary of coding practice).

        Back to the OPs original question: how do you pass data to your templates? While our off-shoot discussion of MVC is interesting, it isn't answering the question. Your first response to the OP implied that the template IS the view (he asked how to prepare data to pass to the template, you replied he shouldn't do that as the view's job is to serialize the data, thus implying the template IS the view). My answer to the question was, don't pass objects. Hopefully that is clear. Where the data passed gets prepared is another question.

        You'll remember I said that the lines of where the view begins is actually a subject of debate. Some pedantically apply it only to the template (such as your earlier post suggests). Some argue that the view begins with a view encapsulating module (such as your later posts have suggested). Others give the view little more recognition that a few subs.

        My quip about SEVCA was aimed at people who pass objects to their templates, treating their templates as the entire view. You are welcome to do so, the template engines available are certainly powerful enough for you to do so, and I can't argue against your usage of it, but I will never recommend passing objects to your templates. I avoid doing so. What I pass to the template is a contract. If I don't establish the contract, the template will take me for all I'm worth.

        I apologize to the OP for getting caught up in the MVC discussion. One of my strongest issues with Catalyst is that it is set upon strict MVC - but everybody has different feelings for MVC boundaries and implementations. As example: pedantic MVC would require at least three classes/modules to request an email address and send an email, while observation would show you can do valid MVC with three subroutines, if one looks at patterns. Life is too short to worry about whether a solution is academically correct in the Computer Science sense, or where a solution gets the job done and is maintainable in the Programming is craft sense. Sometimes we are lucky and it is both.

        As an aside, to help discredit myself even more, I do not use DBIC or other ORMs. I do not know if it is related, but I have never had to create a View Serialization class (the thought of being in a situation that would require it tires me).

        On the last note, I have enjoyed your posts. You seem to have a very good working knowledge. ++

        my @a=qw(random brilliant braindead); print $a[rand(@a)];
Re: How to organize Catalyst stash
by perl5ever (Pilgrim) on Nov 18, 2010 at 15:25 UTC
    For the URLS and textual descriptions of payment values I would create other objects which provide that information.

    For instance, to translate payment type codes into human readable text, have an object available to the template which when given a payment code returns the associated textual description.

    In fact, a payment type code is an example of an "enumeration type", and your application probably has several instances of enumerations. So this is an object you could generalize and use for all of your enumerations. Having a single object would also simplify making this service available to the template.

    This would work very much like the way localization is implemented in templates.

Re: How to organize Catalyst stash
by dwm042 (Priest) on Nov 19, 2010 at 02:29 UTC
    When I'm not using a JSON view, I pass data as objects. But the JSON view doesn't handle objects well, so then I serialize the objects and pass hashrefs.

    Since most of my more interesting pages have plenty of jQuery and Ajax calls, passing objects becomes more or less impossible.

    David.

      That means you should write specialized view classes and call that view from the controller, instead of doing the serialization in the controller.

      daniel
        I don't serialize in the controller. All my controllers do is pass data to the view.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://872182]
Approved by moritz
Front-paged by Arunbear
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others cooling their heels in the Monastery: (3)
As of 2023-06-03 10:45 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    How often do you go to conferences?






    Results (12 votes). Check out past polls.

    Notices?