[Catalyst] In search of RESTful CRUD holy grail

Moritz Onken onken at houseofdesign.de
Wed Mar 4 12:45:44 GMT 2009


Am 04.03.2009 um 13:25 schrieb Zbigniew Lukasiak:

> This might be a futile mission - but I think I learn something with
> each recurrence of this discussion.  So let's restart it once again.
>
> CatalystX::CRUD::REST has a well described RESTful URI structure:
>
>    # POST          /foo                -> create new record
>    # GET           /foo                -> list all records
>    # PUT           /foo/<pk>           -> update record
>    # DELETE    /foo/<pk>           -> delete record
>    # GET          /foo/<pk>           -> view record
>    # GET          /foo/<pk>/edit_form -> edit record form
>    # GET          /foo/create_form    -> create record form
>
> I think everyone will agree that this is indeed REST.  For some cases
> there might be a slight problem with that URI schema - if you have a
> string primary key - and it can have the value of 'create_form' - then
> you will not know if the client is requesting a view on the object
> with that id or if he wants the page with the form to create a new
> object.
>
> This leads to new variation:
>
>    # POST          /foo                -> create new record
>    # GET           /foo                -> list all records
>    # PUT           /foo/by_id/<pk>           -> update record
>    # DELETE    /foo/by_id/<pk>           -> delete record
>    # GET          /foo/by_id/<pk>           -> view record
>    # GET          /foo/by_id/<pk>/edit_form -> edit record form
>    # GET          /foo/create_form    -> create record form
>
> Of course 'by_id' could be 'instance' or whatever (perhaps the library
> should let the user to choose that infix part).
>
> Now   '/foo/by_id/<pk>/edit_form' needs to make a PUT request to
> /foo/by_id/<pk>.   To get around the problem with requests outside of
> the basic 'GET' and 'POST' repertoir we can use
> Catalyst::Request::REST::ForBrowsers.   But it also means that for
> browser requests in the case of mistakes made in the form the action
> answering that PUT request to '/foo/by_id/<pk>' needs to render the
> form again with error indicators. But for non browser requests in case
> of similar errors it needs to encode those errors in some other way.
> This shows that 'edit_form' is only informing us how to *render* (or
> serialize) the data. In the case of GET requests it informs us that we
> should render the object data inside of a form and in the case of a
> PUT request with errors it informs us that we should similarly put the
> error data into the form.  This leads to next variation:
>
>
>    # PUT           /foo/by_id/<pk>                  -> update record
> or return the error encoded for non-browsers
>    # PUT           /foo/by_id/<pk>/edit_form  -> update record  or
> return the error encoded as HTML form
>    # DELETE    /foo/by_id/<pk>                  -> delete record
>    # GET          /foo/by_id/<pk>                  -> view record
> (for non-browsers encoded)
>    # GET          /foo/by_id/<pk>/edit_form  -> view record encoded
> as an edit form
>
> And similarly:
>
>    # POST          /foo                     -> create new record
>    # POST          /foo/create_form -> create record form
>    # GET           /foo                      -> list all records
>    # GET          /foo/create_form   -> create record form
>
> i.e. - here we treat 'edit_form' and 'create_form' consistently as a
> path info parameter informing us about the serialization method.   The
> advantage of this arrangement is also that we don't need to put any
> address into the 'action' parameter of the form - and let it always
> submit to itself.  In another variation we could move that parameter
> from path info to real parameters and have uris like:
>
>    # PUT           /foo/by_id/<pk>                                ->
> update record  or return the error encoded
>    # PUT           /foo/by_id/<pk>?x-view=edit_form   -> update
> record  or return the error encoded as HTML form
>    # DELETE    /foo/by_id/<pk>                                ->  
> delete record
>    # GET          /foo/by_id/<pk>                                 ->
> view record (encoded)
>    # GET          /foo/by_id/<pk>?x-view=edit_form    -> view record
> encoded as an edit form
>
>
> Finally we need serialisation and dispatching to different methods for
> the same address (/foo/by_id/) based on the method.  This is covered
> by Catalyst::Controller::REST.   What I have not yet explored is how
> to add this new serialisation method (to 'edit_form') to the
> configuration of a Catalyst::Controller::REST based controller.
>

Hi,

here are my thoughts on this:

I don't distinguish between edit_form and create_form. If there is a
primary key or similar avaiable (either in the path or in $c->req- 
 >param)
I show the edit form, if there is no such value I show the create form.

Furthermore I would not put the form rendering in the same path as the
REST interface.

I would do something like this:


    # POST         /foo                -> create new record
    # GET          /foo                -> list all records
    # PUT          /foo/<pk>           -> update record
    # DELETE       /foo/<pk>           -> delete record
    # GET          /foo/<pk>           -> view record
    # GET          /form/foo/<pk>      -> edit record form
    # GET          /form/foo           -> create record form

So now, what do we do with errors and redisplay of forms.
I would say a form should post / put / delete to the rest api.
Within that controller we can easily see if a browser is requesting
this url or a web service (see ForBrowser's looks_like_browser()).
If it's a browser we can detach to the /form/foo controller.
Otherwise we show the errors in the format of the Accept header.

moritz



More information about the Catalyst mailing list