[Catalyst] Best practices: XML output from static XML

Bill Moseley moseley at hank.org
Sun Mar 7 17:03:33 GMT 2010


On Sat, Mar 6, 2010 at 2:33 PM, J. Shirley <jshirley at gmail.com> wrote:

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.
>

That sounds good.  So, what does that exactly look like?  Can you help with
a few examples?

These are the goals:

   - Don't repeat action code.  i.e. don't have separate actions for REST
   and web requests for the same resource.
   - Same URLs for the same data (e.g. /blog/recent_posts) for both the REST
   actions and when rendering with TT.
   - Separate out View from Controller.  i.e. don't have actions build data
   structures that won't be used in the response.
   - Web browser requests differ from REST requests, so may need to map the
   request into a common format before running the action.
   - Clean and tidy controllers ;)

Here's two very simple actions.  The first is in a REST app that needs to
add a TT view.  The second is an existing web (browser) action that need to
extend to accept JSON input and generate JSON output (i.e. convert to using
::REST).


First, here's a pseudo REST action for recent blog posts.  How would you
modify or extend so it works with a TT view?

Assume that the TT view needs more data from the posts (i.e. include \@posts
in the stash), and always returns a 200 since the page will always have
content.  Also, the TT view has no need for the "entity" structure created
in this existing action:

sub recent_posts : Local ActionClass( 'REST' ) {}

sub recent_posts_GET {
    my ( $self, $c ) =3D @_;

    my @posts =3D map {
        {
            id      =3D> $_->id,
            body    =3D> $_->description,
            created =3D> $_->created_time->iso8601_with_zone,
        }
    } $c->model( 'Blog::Post' )->recent;

    return @posts
        ? $self->status_ok( $c, entity =3D> { posts =3D> \@posts } )
        : $self->status_no_content( $c ) unless @posts;

}


Or going the other direction, here's an existing action used by web browsers
to view a blog, create a blog, or update an existing one.

A web form POSTs to /blog to create and POSTs to /blog/1234 to update.
 The request parameters are validated and used to update/create the blog.
The action builds a "$form" object that used for this purpose and is used by
TT to render the form.

Note that a GET to this action works, too.  It simply returns a form object
in the stash which TT can use to display the post (or can get at the blog
object
directly with $form->object).  But, would be better to chain from an action
with Args(1).


sub blog : Path {
    my ( $self, $c, $id ) =3D @_;

    # is user authorized for this method?
    return $c->res->status( 403 )
        unless $self->authorize( $c ):

    # build form object and place in stash;
    my $form =3D Form::Blog->new(
        $id,  # will be undefined for CREATE
        $c->req->parameters,
    );
    $c->stash->{form} =3D $form;


    # GET requires an id to a valid blog
    return $c->status( 404 )
       if $c->req->method eq 'GET' && !$form->object;


    # was form POSTed?
    return unless $c->req->method eq 'POST';

    # redisplay page if does not validate
    return unless $form->validate;

    # write data to model
    $form->update_or_create;

    # Redirect to show new post:
    my $uri =3D $c->uri_for( 'blog', $form->object->id );
    return $c->res->redirect( $uri );
}



Now, for REST it's a POST to /blog to create and NO arguments are allowed,
and for update it's a PUT to /blog/$id and $id is REQUIRED.  The request
parameters are in a "blog_data" key in the JSON request.  For create return
201 with a Location: header.  On update probably return a 204.

If the request parameters do not validate (as tested by the form) then need
to serialize the form's errors in a JSON response.

Much of the existing blog action can be used for both the POST and PUT (and
probably GET), but need to map the "blog_data" into parameters used by the
form object.

That seems a bit more straight forward -- just add blog_<$METHOD> methods
that (re-set) the status code (and for GET) builds the "rest" stash.  But,
still need a way to extract out the "blog_data" from $c->req->data and use
it as the request parameters.



> 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.
>

The main issue is just finding a clean way to handle both web browser and
REST request and responses.

But specifically, two issues:

First, input munging is often required.  I want to share action code for
processing input data but the web request comes in $c->req->parameters, but
with ::REST the request data comes in $c->req->data and may be formatted
slightly differently or with different parameter names.

Second, the ::REST approach has the controller actions building the "entity"
structure which feels more like a view operation.  Minor point but turning,
say, a DateTime object into a human-readable format (or ISO8601 for JSON
response) both seem like views.




-- =

Bill Moseley
moseley at hank.org
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.scsys.co.uk/pipermail/catalyst/attachments/20100307/96b16=
291/attachment.htm


More information about the Catalyst mailing list