Database Backed Voting Booth
In theory, this is a well commented walk-through of how one might impliment a flexible voting booth in AOLserver. My purpose isn't to provide a plug-in voting booth module but rather to demonstrate one style of AOLserver programming. I don't know what the best way to present this is, so feel free to edit (after all, that's what a wiki is all about).
ns_register_proc GET /voting vote_subjects
ns_register_proc GET /voting/new_subject new_subject
ns_register_proc GET /voting/place_vote place_vote
ns_register_proc GET /voting/delete_subject delete_subject
ns_register_proc GET /voting/dovote dovote
ns_register_proc GET /voting/view view_vote
Ok, mockery of my questionably named procedures aside here is the overview. My preference is to have each .tcl file impliment one collection of related functions. In this case all the code specific to the Voting booth is in one file. I like to put all the ns_register_proc's right at the top so I always know where to look when correlating a URL with it's procedure.
Six URL's for a voting booth might seem excessive, let me explain the purpose of each first and then we'll get to the code itself (the code is the easy part).
/voting -- this is the top-level url for the voting booth. It presents the first voting-related page I want the end users to see. Some folks might prefer /voting.html (which has some advantages, even the dumbest browsers won't have any trouble mime-typing it) but I like to only use .html extentions for static pages. Maybe that's weird.
/voting/new_subject -- When a user wants to add a new subject for voting this is the URL called in the <form action= field (lets see how the wiki handles broken html...). The relevant form values are described in the comments near the "new_subject" procedure. I used to comment form values up here near the ns_register_proc but I've found that when I do that, they don't stay current (my lazyness or an inherent inefficiency problem, I'll declare the latter rather than risking the former).
/voting/place_vote -- this is a badly named url/procedure. It's badly named because it's actually presenting all the possible voting choices, not placing an actual vote. A better name would be "present_choices".
/voting/delete_subject -- I really like to empower users to maintain a web site by themselves. If I leave for a month, I don't want to see piles of cruft. So the voting booth allows whichever user created a given topic to delete it. This is the <form action reciever for a user deleting one of their voting topics.
/voting/dovote -- actually place_vote would be better but I already mis-used that one. This does the actual voting. It's somewhat complex because I want users to be able to change their votes after the fact. The method I'm using to allow for such changes is highly questionable (it works, but I have every reason to believe it doesn't scale). The ability of a site to scale nicely is really underrated. Bad algorithms (like the one I'm using here) can work for a small site with fifty hits a day, but if you get pounded for some reason (slashdot-effect) you're dead in the water. Another important scaling concern isn't hits, but expandability. In this case, if there are more than a couple dozen voting subjects the page gets so cluttered and some of the DB lookups get so slow that the usability of the site is flushed.
/voting/view -- finally a nice simple one. view_vote displays a quick summary of the voting results for a given subject. I tossed in a hack to allow this to also display results immediatly after someone votes with their vote highlighted. Yes, it's a hack. I call it that not because I'm risking DMCA violations but rather because it breaks the paradygm under which the module was designed. /voting/dovote doesn't actually ns_return anything. Instead it hands off to /voting/view; a dangerous choice because a problem might be more difficult to track down (it may not be immediatly apparant if the problem is in dovote or view), resulting in a potential for extra work down the road.
I can hear the database experts out there groaning. There are a number of flaws (one glaring flaw) in these two tables. I like to create procedures like this because when you're coding by the seat of your pants the schema is going to get tweaked. Dropping the tables and sequences by hand is quick but recreating them is a hassle.
Now for the procedures that make up the voting booth. First the entire vote_subjects procedure then I'll pull it apart and explain the odd parts.
I'll be deliriously optimistic and assume that the entire procedure make sense with the exception of these two lines:
set userrow user $db $conn append subject_lines template "voting_subject.html" $content_set
The first calls the "user" procedure which actually checks against a cookie to see who is logged on, validates their password and returns an ns_set of information about the user (simple enough).
The "template" procedure is outragously useful. Here it is:
That's it. It takes a file and an ns_set of key-value pairs. It replaces every instance of %%%%key%%%% with the corresponding value from the ns_set. This lets you create your page in _any_ html editor, just put %%%%keyname%%%% in for anything that needs to be dynamic. The alternatives are ADP or hardcoding html. ADP is a pretty good solution but I like to have a stronger barrier between code and data. Actually the latest Dr Dobbs (Feb 2002) has an article about Zope's approach to this very question.
Why do your own variable interpolation yourself, when you could have just used ns_adp_include of your template files, and used <% ns_set get key %> wherever appropriate, instead? Just curious ... -- Dossy
I ought to. ;) It's possible my template procedure is a little faster (no eval), and I can give users access to editing the template files without huge potential for destruction. But I still ought to switch. Jason Kane
In case that wasn't quite clear, here are the templates themselves:
Now that we know how to display all the voting topics, we need to actually let people vote. But before they can vote, they need to know what the options are. Since this is slick-n-smooth voting booth, they also need to be able to add new voting options. While we're at it, if this user has already voting on this subject we want to re-present all of the options but default to whatever they chose last time.