<br><br><div class="gmail_quote">On Sat, Mar 6, 2010 at 2:33 PM, J. Shirley <span dir="ltr"><<a href="mailto:jshirley@gmail.com">jshirley@gmail.com</a>></span> wrote:</div><div class="gmail_quote"><br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">
This is where I actually really love ::REST. If you make a JSON call,<br>
then whatever is in the stash key that ::REST is returned to the<br>
client (via ->status_ok) -- but if you combine this with chained and<br>
setup other data in the stash, the TT view can access all of that.<br></blockquote><div><br></div><div>That sounds good. So, what does that exactly look like? Can you help with a few examples? </div><div><br></div><div>
These are the goals:</div><div><ul><li>Don't repeat action code. i.e. don't have separate actions for REST and web requests for the same resource.</li><li>Same URLs for the same data (e.g. /blog/recent_posts) for both the REST actions and when rendering with TT.</li>
<li>Separate out View from Controller. i.e. don't have actions build data structures that won't be used in the response.</li><li>Web browser requests differ from REST requests, so may need to map the request into a common format before running the action.</li>
<li>Clean and tidy controllers ;)</li></ul><div>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).</div>
<div><br></div><div><br></div></div><div>First, here's a pseudo REST action for recent blog posts. How would you modify or extend so it works with a TT view?</div><div><br></div><div>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:</div>
<div><br></div><div><font class="Apple-style-span" face="'courier new', monospace">sub recent_posts : Local ActionClass( 'REST' ) {}</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br>
</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><div><div>sub recent_posts_GET {</div><div> my ( $self, $c ) = @_;</div><div><br></div><div> my @posts = map {</div><div> {</div>
<div> id => $_->id,</div><div> body => $_->description,</div><div> created => $_->created_time->iso8601_with_zone,</div><div> }</div><div> } $c->model( 'Blog::Post' )->recent;</div>
<div><br></div><div> return @posts</div><div> ? $self->status_ok( $c, entity => { posts => \@posts } )</div><div> : $self->status_no_content( $c ) unless @posts;</div><div><br></div><div>}</div>
<div><br></div></div></font></div><div><br></div><div>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.</div><div><br></div><div>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.</div>
<div>The action builds a "$form" object that used for this purpose and is used by TT to render the form.</div><div><br></div><div>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</div>
<div>directly with $form->object). But, would be better to chain from an action with Args(1).</div><div><br></div><div><br></div><div><div><font class="Apple-style-span" face="'courier new', monospace">sub blog : Path {</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> my ( $self, $c, $id ) = @_;</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # is user authorized for this method?</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> return $c->res->status( 403 )</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> unless $self->authorize( $c ):</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # build form object and place in stash;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> my $form = Form::Blog->new(</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> $id, # will be undefined for CREATE</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> $c->req->parameters,</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> );</font></div><div>
<font class="Apple-style-span" face="'courier new', monospace"> $c->stash->{form} = $form;</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br>
</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # GET requires an id to a valid blog</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> return $c->status( 404 )</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> if $c->req->method eq 'GET' && !$form->object;</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br>
</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # was form POSTed?</font></div><div>
<font class="Apple-style-span" face="'courier new', monospace"> return unless $c->req->method eq 'POST';</font></div><div><font class="Apple-style-span" face="'courier new', monospace"><br>
</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # redisplay page if does not validate </font></div><div><font class="Apple-style-span" face="'courier new', monospace"> return unless $form->validate;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # write data to model</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> $form->update_or_create;</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"><br></font></div><div><font class="Apple-style-span" face="'courier new', monospace"> # Redirect to show new post:</font></div><div><font class="Apple-style-span" face="'courier new', monospace"> my $uri = $c->uri_for( 'blog', $form->object->id );</font></div>
<div><font class="Apple-style-span" face="'courier new', monospace"> return $c->res->redirect( $uri );</font></div><div><font class="Apple-style-span" face="'courier new', monospace">}</font></div>
</div><div><br></div><div><br></div><div><br></div><div>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.</div>
<div><br></div><div>If the request parameters do not validate (as tested by the form) then need to serialize the form's errors in a JSON response.</div><div><br></div><div>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.</div>
<div><br></div><div>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.</div>
<div><br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex;">Just curious, but if you apply the REST action class (and define<br>
_GET/_POST methods) and keep stash population to your actions, what is<br>
failing there? I've done similar things to what you are discussing<br>
(and will again in about a week or two from now) and hadn't run into<br>
anything severe.<br></blockquote><div><br></div><div>The main issue is just finding a clean way to handle both web browser and REST request and responses. </div><div><br></div><div>But specifically, two issues:</div><div>
<br></div><div>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.</div>
<div><br></div><div>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. </div>
<div><br></div><div> </div><div><br></div><div> </div></div>-- <br>Bill Moseley<br><a href="mailto:moseley@hank.org">moseley@hank.org</a><br>