On Sat, Mar 6, 2010 at 2:20 PM, Bill Moseley <mose...@hank.org> wrote: > > > On Sat, Mar 6, 2010 at 12:59 PM, J. Shirley <jshir...@gmail.com> wrote: >> >> I don't think the path taken with Catalyst::Action::REST is the best, >> but it does work very very well in my opinion and I certainly can't >> think of anything better. Being able to send to a serialization >> method based on the content-type solves a lot of these issues. You >> could just setup a content type for your feed in configuration and >> write a custom serialize class and get exactly what you are asking >> for. > > I agree, ::REST works well for what it does, but doesn't provide a framework > for a View that is action-specific. ::REST assumes that the actions set an > entity in the "rest" stash that can be serialized. But, if the same action > is used for a TT View then probably want to just pass model objects in the > stash instead of creating a "rest" structure in the stash. > One could argue that actions should always result in the same data in the > stash regardless of the View (the Controller has no idea about the final > View). The reality is that a request to /blob/recent_posts would return > different data in a JSON response than would be use to render a web page > with a TT view. >
This is where I actually really love ::REST. If you make a JSON call, then whatever is in the stash key that ::REST is returned to the client (via ->status_ok) -- but if you combine this with chained and setup other data in the stash, the TT view can access all of that. So I view it more that the JSON response is limited to showing *only* the request result, where as TT can show everything else surrounding it. This allows me a great amount of flexibility in my templates, without having any [% c.model('Schema::Whatever') %] in them. > > The "problem" I'm up against is we have an existing application written for > the web -- so actions expect GET and POST requests and place objects in the > stash. Then TT uses the objects in the stash to render the markup. > Now we need to expose these same methods (which means same URLs really) for > two similar purposes. New development for the web app is all client side. > Fat AJAX that talks to the application via JSON serialized (mostly) > requests and responses. Plus, we need to expose a REST API for third party > customers. So, really it's just an API for both. > ::REST will work fine for new actions, but there's a lot of existing actions > that need to work both for TT rendered pages and for JSON responses. > I think the action's job should be to take a request, validate, > authenticate, authorize, etc, then either generate error or place model > objects in the stash. Then pass off to the view to render/serialize. > The problem is that a request coming from a browser may be slightly > different than from an AJAX or API request. (Request might come in a as a > POST on the web and a PUT via the API and parameters might be slightly > different.) So, either need to dispatch to different actions for same > request URL or have some kind of filtering code that runs before the action > to "normalize" the request for the action depending on where it's coming > from. > Obviously, it makes sense to share the actions where possible. > Likewise, the TT View passes control to an action-specific template to > render the markup for the request, but the same action might need to return > JSON so in that case would need to also have action-specific code to build > the json stash from the objects the action fetched. Just curious, but if you apply the REST action class (and define _GET/_POST methods) and keep stash population to your actions, what is failing there? I've done similar things to what you are discussing (and will again in about a week or two from now) and hadn't run into anything severe. As long as the content-type (no problem for XmlHttpRequest) is set to "application/json" I get back the data I expect. I do have to modify some of the actions to set the final stash result, but in most cases this is just a single line of code. >> >> I don't want to evangelize ::REST too much, so to address your >> >> suggestions more directly I'd have to say that relying on $self->can >> >> seems a bit too limited for my tastes. > > I don't like it either. Still, to me it seems the act of taking the model > objects loaded by the controller and building the "json" stash is a View > action -- not something that should happen in the controller action. > >> I'd lean on configuration more so than $self->can. Then a call to >> $self->get_serializer_for('JSON') that returns some serialization >> object (or whatever handler you have) is simple, and coupling it with >> Moose would work very well. Then you can work out adding new >> serialization calls just in config. > > So are you suggesting that $self->get_serializer_for('JSON') would return > code that would be action-specific? That is for a request to > /blog/recent_posts would return code that would know what to put in the > "json" stash for that specific request? > I like the idea of leaning on the configuration implementation -- just not > sure what that looks like. ;) > The very barest example: __PACKAGE__->config( serializers => { 'JSON' => sub { return "this is icky, but would work" } } ); has 'serializers' => ( is => 'rw', isa => 'HashRef', traits => [ 'Hash' ], handles => { 'get_serializer_for' => 'get' } ); Then you could do something like: $self->get_serializer_for('JSON')->($c); (Untested, and I would use coercions to setup a secondary class to handle, but again.. at this point just having Views and end actions seems more sensible :)) > >> >> However, I'm having a hard time thinking about any valid use cases for >> this, especially since ::REST does things fairly well (especially for >> how old the code is) so I'd automatically use that for all the cases I >> can think of. Anything else that doesn't fit, I'd just defer to having >> separate views (and possibly a different RenderView+end action as >> appropriate). > > If I was starting fresh I'd be tempted to write all the controller actions > with ::REST. I'd like it if the various _<METHOD> actions were real actions > for dispatching (they aren't right?). Real actions would mean could test > things like Args: > sub blog_GET : Local Args(1) { # GET requires an argument > sub blog_POST: Local Args(0) { # POST creates and must not have an > argument > sub blog_PUT : Local Args(1) { # PUT requires and argument > sub blog_DELETE : Local Args(1) { They aren't normal actions, and they shouldn't be because they're handlers for a specific request type. There isn't anything stopping you from doing $controller->blog_GET($c) though. If you have variable arguments, then you are pointing to a different resource and thus what you described wouldn't be REST :) > Then layer the web app on top -- especially if it's all client side. But, > again I still think the action's job would be to just place model objects in > the stash -- not build a "json" structure as that is only needed when the > response is actually json. > _______________________________________________ List: Catalyst@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/