<br><br><div class="gmail_quote">On Sat, Mar 6, 2010 at 2:33 PM, J. Shirley <span dir="ltr">&lt;<a href="mailto:jshirley@gmail.com">jshirley@gmail.com</a>&gt;</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 -&gt;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&#39;t repeat action code.  i.e. don&#39;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&#39;t have actions build data structures that won&#39;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&#39;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&#39;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 &quot;entity&quot; structure created in this existing action:</div>

<div><br></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">sub recent_posts : Local ActionClass( &#39;REST&#39; ) {}</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br>

</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, 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      =&gt; $_-&gt;id,</div><div>            body    =&gt; $_-&gt;description,</div><div>            created =&gt; $_-&gt;created_time-&gt;iso8601_with_zone,</div><div>        }</div><div>    } $c-&gt;model( &#39;Blog::Post&#39; )-&gt;recent;</div>

<div><br></div><div>    return @posts</div><div>        ? $self-&gt;status_ok( $c, entity =&gt; { posts =&gt; \@posts } )</div><div>        : $self-&gt;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&#39;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 &quot;$form&quot; 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-&gt;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="&#39;courier new&#39;, monospace">sub blog : Path {</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    my ( $self, $c, $id ) = @_;</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br></font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # is user authorized for this method?</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    return $c-&gt;res-&gt;status( 403 )</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">        unless $self-&gt;authorize( $c ):</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br></font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # build form object and place in stash;</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    my $form = Form::Blog-&gt;new(</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">        $id,  # will be undefined for CREATE</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">        $c-&gt;req-&gt;parameters,</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    );</font></div><div>

<font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    $c-&gt;stash-&gt;{form} = $form;</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br></font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br>

</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # GET requires an id to a valid blog</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    return $c-&gt;status( 404 )</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">       if $c-&gt;req-&gt;method eq &#39;GET&#39; &amp;&amp; !$form-&gt;object;</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br>

</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br></font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # was form POSTed?</font></div><div>

<font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    return unless $c-&gt;req-&gt;method eq &#39;POST&#39;;</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br>

</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # redisplay page if does not validate </font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    return unless $form-&gt;validate;</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br></font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # write data to model</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    $form-&gt;update_or_create;</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace"><br></font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    # Redirect to show new post:</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    my $uri = $c-&gt;uri_for( &#39;blog&#39;, $form-&gt;object-&gt;id );</font></div>

<div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">    return $c-&gt;res-&gt;redirect( $uri );</font></div><div><font class="Apple-style-span" face="&#39;courier new&#39;, monospace">}</font></div>

</div><div><br></div><div><br></div><div><br></div><div>Now, for REST it&#39;s a POST to /blog to create and NO arguments are allowed, and for update it&#39;s a PUT to /blog/$id and $id is REQUIRED.  The request parameters are in a &quot;blog_data&quot; 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&#39;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 &quot;blog_data&quot; into parameters used by the form object.</div>

<div><br></div><div>That seems a bit more straight forward -- just add blog_&lt;$METHOD&gt; methods that (re-set) the status code (and for GET) builds the &quot;rest&quot; stash.  But, still need a way to extract out the &quot;blog_data&quot; from $c-&gt;req-&gt;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&#39;ve done similar things to what you are discussing<br>
(and will again in about a week or two from now) and hadn&#39;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-&gt;req-&gt;parameters, but with ::REST the request data comes in $c-&gt;req-&gt;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 &quot;entity&quot; 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>