views:

105

answers:

4

Its been mentioned in other answers about getting the same code running for both the def get(self) and the def post(self) for any given request. I was wondering what techniques people use, I was thinking of:

class ListSubs(webapp.RequestHandler):
    def get(self):
        self._run()

    def post(self):
        self._run()

    def _run(self):
        self.response.out.write("This works nicely!")
+2  A: 

Refactoring the code that does the work into its own function/method is the correct method.

Ignacio Vazquez-Abrams
Ok, so is that basically what I've done above?
Peter Farmer
That is correct.
Ignacio Vazquez-Abrams
+1  A: 

I've used this:

class ListSubs(webapp.RequestHandler):
    def post(self):
        self.response.out.write("This works nicely!")
    def get(self):
        self.post()
Drew Sears
While this does work, it does slightly impinge upon the Principle of Least Astonishment.
Ignacio Vazquez-Abrams
+9  A: 

I would suggest both theoretical and practical reasons why the approach you're using (refactoring out the common code to a separate method and calling it from both post and get methods) is superior to the apparently-simpler alternative of just having one of those two methods call the other.

From a theoretical viewpoint, "method A entirely delegates to method B" implies a notion of "primacy" or "asymmetry" -- a design decision that, going forwards, any change that may be applied to B will inevitably, intrinsically apply to A as well; that A may in the future be slightly customized with respect to B (adding some extra code before and/or after A's call to B) but never vice versa. When there's no reason to expect such primacy it's a bad coding decision to embed that notion in your code. By having both A and B call the common private method C, you avoid breaking symmetry.

Some people are not happy with theoretical arguments and prefer pragmatic ones: fortunately, in this case, the theoretical translates to the pragmatic pretty directly. Again, it's an issue of future evolution of the code: having both A and B call C leaves you all needed degrees of freedom to do small customizations (adding code before and/or after the call to C) to either, both, or neither of A and B. Since you don't know which parts of this flexibility you will need, and the cost of it in terms of simplicity is miniscule, taking the simple and flexible route is highly pragmatical and advisable.

One last pragmatical point (applying to either choice): any time you have a pattern like:

def amethod(self):
    return cmethod(self)

you're usually (modestly) better off rewording this as

amethod = cmethod

This avoids an unneeded level of call nesting (flat is better than nested). So, your class could usefully be coded:

class ListSubs(webapp.RequestHandler):

    def _run(self):
        self.response.out.write("This works even better!")

    get = post = _run

No big deal, and you'll have to refactor back to the original "nested" way if and when you do need to apply tweaks before or after the nested call (from get to _run, etc) or need other tweaks in debugging (e.g. set a breakpoint in your debugger on post but without having the breakpoint trigger on get, etc), but a nice little simplification for those times where it's feasible.

Alex Martelli
Thanks, thats not a lesson I'll forget in a while!
Peter Farmer
+1  A: 

One thing that I haven't seen in responses, which I'll throw in, is why you shouldn't do this. It is a fairly common principal that an HTTP GET changing data on the server is a bad idea. Changing the server's state should usually happen through a POST. As a result, each URL which is used for both GET and POST should have specific actions that differ based on the type of request. The w3c has a nice overview of when to use GET vs. POST as well.

I tend to think of GET and POST like a getter and a setter. POSTs change data, and GETs get data. As a result, a simple search can use GET all day long, but when you save a setting back to the server, a POST is in order.

For example, say you have a URL for a post in your blog (example.com/blog/post-vs-get/). You could then use get() to get the blog post and render it to screen with a nice comment form. In this example, my comment form will POST back to the same URL, calling the post() method, which can process the form and then return the same rendered page.

class ListSubs(webapp.RequestHandler):
    def post(self):
        comment = cgi.escape(self.request.get('comment'))
        ## Do magic with our comment.
        self.get() ## Go off and return the rendered page.
    def get(self):
        ## Get the blog post out of the data store and render a page.
        self.response.out.write("""<html>
              <body>
                <p>My awesome blog post!</p>
                <form method="post">
                  <h1>Comment</h1>
                  <textarea name="comment" rows="3" cols="60"></textarea>
                  <input type="submit" value="Comment">
                </form>
              </body>
            </html>""")

This gives a clean division of labor between rendering the page, and processing the POST data. This also keeps your code from running unnecessary form validation and/or processing on a request with no data. In my opinion, this separation of duties also makes debugging and maintaining code easier.

Jack M.
On a separate note, you should probably do a redirect after the POST to keep people from accidentally refreshing the page and posting again.
Jack M.