[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