Posts Tagged ‘restful’

CakePHP RESTFul Resources, Not There Yet.

March 5th, 2009 by ScottK | 3 Comments | Filed in cakePHP

Now that CakePHP 1.2 is a stable version I’ve picked back up on it. When I looked at it as a release candidate, and after they announced RESTFul routing, I specifically looked at the RESTful implementation. I’m looking at it again and find it’s still not there.

Looking at the router.php file for the Router::mapResources :

var $__resourceMap = array(
array('action' => 'index', 'method' => 'GET',  'id' => false),
array('action' => 'view',  'method' => 'GET',  'id' => true),
array('action' => 'add',  'method' => 'POST',  'id' => false),
array('action' => 'edit',  'method' => 'PUT',  'id' => true),
array('action' => 'delete',  'method' => 'DELETE', 'id' => true),
array('action' => 'edit',  'method' => 'POST', 'id' => true)
);

The first thing pops out and smells is the double entry for the edit action. This clearly indicates that I’ll need to put in conditional logic to either present the edit view or save a posted edit form. One of those edit methods needs to be changed to an update action to remove the edit conditionals.

There are actions missing from the mapResources as well. Where’s the new(create) action? This presents a view with the fields for creating a new record of whatever. Certainly submitting the form would POST to the add action.

As it stands and as advertised the mapResources does work. /posts calls the index action. /posts/1 calls the view action. So how can the two missing actions (update / new) be added easily?

The original config/routes.php file would look like this:

Router::mapResources('posts');

Just add two lines above the mapResources:

Router::connect("/:controller/:id", array("action" => "update", "[method]" => "PUT"),array('pass' => array('id'));
Router::connect("/:controller/new", array("action" => "create", "[method]" => "GET"));

//Original
Router::mapResources('posts');

As long as those two lines are above any mapped resources they will all act the same. So:

Router::connect("/:controller/:id", array("action" => "update", "[method]" => "PUT"),array('pass' => array('id'));
Router::connect("/:controller/new", array("action" => "create", "[method]" => "GET"));
Router::mapResources('posts')
Router::mapResources('users');

Will apply the new update and create routing for both posts and users.

There is one other thing that is bugging me about the cakePHP RESTFul implementation. The PUT/POST method to call an edit view. I want to be able to call posts/1/edit with a GET to display the edit view. So with one more line of code in the routes.php file.

Router::connect("/:controller/:id/edit", array("action" => "edit", "[method]" => "GET"),array('pass' => array('id'));
Router::connect("/:controller/:id", array("action" => "update", "[method]" => "PUT"),array('pass' => array('id'));
Router::connect("/:controller/new", array("action" => "create", "[method]" => "GET"));
Router::mapResources('posts')
Router::mapResources('users');

Now we have RESTFul routing for these routes!

URL Method Action
/posts GET index()
/posts POST add()
/posts/new GET create()
/posts/1 GET view($id)
/posts/1 PUT update($id)
/posts/1/edit GET edit($id)
/posts/1 DELETE delete($id)
/users GET index()
/users POST add()
/users/new GET create()
/users/1 GET view($id)
/users/1 PUT update()
/users/1/edit GET edit($id)
/users/1 DELETE delete($id)

Tags: , , ,

CherryPy, RESTFul Routing

October 12th, 2008 by ScottK | 2 Comments | Filed in CherryPy

Using CherryPy as a web server allows us to quickly create services and gain a lot more free time, or rest. I don’t miss those times where we worked none stop on other frameworks to provide services to other apps.  OK moving from on project immediately to another project takes up that free time and the rest in between is not what I mean by RESTFul routing.

The buzzword RESTFul routing is really just a concept that states actions that you perform for updating your data can be attributed to the state the browser asks you to do it. Along with parameters that it sends. I won’t go into any full explanation of what Representational State Transfer is, but you can find a good explanation here RESTFul.

I do find that that having RESTFul controllers are a big benefit to integrating with Ruby on Rails apps, yet I’m not so hooked on the idea that RESTFul is the end all be all of web programming. There are time in even rails apps that you need to break that convention and create special routing configurations. CherryPy, through the routes, fully supports those as well.

I’ve created the downloadable CherryPy Example 3 so you can follow along.

Let’s start by looking at the natural RESTFul controller: controllers/users_controller.py. You’ll find that it holds the full RESTFul actions you would expect to find. Create, Index, Show, New, Update, Edit, Delete. All of these are completely usable due to the @cherrypy.expose decorator. Look at the controllers/hello_worlds_controller.py as well. You’ll find that none of the RESTFul actions are there at all.

In order to use both of these controllers you need to step outside of the “default” CherryPy tutorial and dig a little deeper. Hence is the reason why you’ll find in the libs folder the route_mapper.py. It’s the core of setting up the RESTFul routing. So let’s walk through the example step by step because it all starts with the deploy.py and follows step by step.

In my last post about CherryPy, The Deploy Script I only setup the first stage of running a CherryPy app. Only hinting on how to set up RESTFul routing. I made mention that two methods were left out. Now those are important.

The first of the two is the “load_default_routes” method. This method does not accept any arguments and setups up all your RESTFul routes by default. The second of the the two is the “load_custom_routes” which is what you’ll use to set up all your custom routing, or named routing.

You’ll notice in the example that I import these controllers during these methods. This is intentional because we absolutely have to have the con figs loaded, example 2. Along with each of the methods we are building a list of dict pertaining to specific information. Name, path, controller for both default and named routes, as well as specific action for the named routes.

In the deploy script the “server_start” method calls these two methods. Which then passes these as arguments to the lib.route_mapper library. This is where the routes magic happens. In lib you’ll find the route_mapper.py file give that a look.

By default CherryPy uses it’s own dispatcher to set up routing. This is not necessarily RESTFul. In that using the default you would have to call /users/index to access the UsersController index action. What we wanted was the RESTFul GET method /users. What about different format types as well? The routes_mapper library throws away the CherryPy dispatcher and uses the routes dispatcher instead.

That what this is doing:

d = cherrypy.dispatch.RoutesDispatcher()

Now that we have the routes library dispatcher for CherryPy we can set up routing as we need using the connect method.

Certainly in setting this up I discovered some really strange things which I’ve fixed in your example. But what it boils down to if you want to set up your own RESTFul routing or named routing is this: Order of Operation.

1. (controller)/:id.:format
2.(controller):id

If I called event/1.html it would find number one first. If I called event/1 it would find number two.

1. (controller):id
2. (controller):id.:format

In either case of calling event/1 or event/1.html it would always find number one.

This is true of all methods as well. The order of Create, Index, New, Show, Edit, Update seems to be the best mix for setting up the Routes mapper. In different orders the correct actions may not be called correctly.

You’ve now seem me use :id, and :format. What do these mean? They are merely the arguments I am passing to the controller action. You can certainly use what ever you need to.

Given that if in my load_default_routes I have:

routes = [
{"name"  : "post_comments",
"path"  : "post/:post_id/comments/:id",
"controller" : CommentsController()
}
]

I would need to have a comments_controller class with a show action. This action would have the arguments of:

def show(self, post_id, id):
...

So anything you prepend with “:” thus becomes an argument to your action method.

Really by taking the routes dispatcher from the default CherryPy dispatcher you can implement RESTFul routing. It really isn’t difficult to do as long as you do it in order of actions and as long as you understand what Representational State Transfer is all about. Even named and custom routes can be used if you wish.

Tags: , , ,

Restful Pylons with Extension Types

June 13th, 2008 by ScottK | No Comments | Filed in Python

The time has come for me to finally use a python web framework, Pylons is the choice around here. I’ve set up a few toy apps using it but nothing so involved that took real software planning. Since I was making a real application that required real business planning I found the Pylons documentation to be really screwed up. The instructions in the tutorials conflicted with each other in how to set up the websetup.py, development.py, etc. Months ago I had a toy program setup in half an hour using one tutorial; today it took me over two hours because the Pylons docs had changed. But not across the boards.

While displeased with this, I’m used to it. It also means that serious research is needed in order to work with the software. Being fully involved with Pylons, albeit new. I didn’t expect it to be RESTful; nor did I expect it to allow different extension type within an action. I am actually impressed with the Ruby on Rails RESTful implementation and was very surprised to find that Pylons itself has RESTful implementations itself.

However, my project requires me to call one action in the extension with “.js” for JSON and another extension of “.rss” for that version. The Pylons RESTful documentation lacks for this. I couldn’t find any other reference for this on the web either. While I don’t know for sure when Pylons added RESTful resources the documentation lacks any examples that I found for using different formats, so here it goes.

The Pylons wiki for RESTful controllers is found here: How map.resource enables controllers as services. It explains everything you want to know about how to use REST within Pylons. It does not explain my predicament on how to make the index action display JSON format or RSS format.

After a little testing and discovery I found out that you can, within an action, produce different views specific to the format of the action.  Given my requirements here is how I was able to use one RESTful action for many formats, i.e (.js, .gif, .rss, .rtf, etc).

1. Create your RESTful controller using the command: paster restcontroller <singular name> <plural name>
This is no different than the instructions in the map.resource link above.

2. In the action create an if…elsif…else to actually control the format:
def index(self, format=”html”):
if format == “js”:
response.headers['Content-Type'] = ‘application/javascript’
return render(’/pubscript/indexjs.mako’)
elif format == “xml”:

response.headers['Content-Type'] = ‘application/xml’
return render(’/pubscript/indexxml.mako’)
else:
abort(404, ‘Page Not Found’)

So as the index definition provides that any index call to the controller does perform the action, only the .js returns an js (JSON) file or an .xml renders an xml file format. Any other format or none provided sends a 404 File Not Found error. Now I had complete control of what the format is and control of any unexpected calls.

Pylons has evoled to the presedence set by Ruby on Rails in that RESTful controllers can be used. Even though the default format for Pylons is HTML you can detect such as present different formats based upon such.  Being new to the Pylon framework itself I wasn’t expecting this functionality. Discovering that it has it, and is acting on the different formats, I do believe that it has emerged as a mature framework that embraces best practices of web application programming.

Still highly complicated in setting up and a huge learning curve with little correct documentation though, don’t get me wrong there.

Tags: , , , , ,