Stellar Chariot

By Arun Neelakandan

Improvements to Phone Based Customer Service

I moved to a new place recently. About 6 weeks prior to moving, a request to cancel our home phone on the 20th of October was made to my phone provider, Telstra. To my surprise, my service was cancelled prematurely on September 20th, a month earlier than expected.

This initiated a stack of pain trying to get my home phone and Internet connection (from a different service provider!) back up and running. After many expensive phone calls, much parroting of the same information ad nauseum and much frustration arising from people who (a) couldn’t take any proper action or (b) just redirected to another department or person, I got my service back up and running again.

Here are some ways to improve the service to a customer:

  • Be careful with destructive operations: When a destructive operation is being made (e.g. cancellation), make certain that the operation is to go ahead and at precisely the right time. Notify the customer before doing the operation.
    • The only exception is if the operation can be undone painlessly, in which case a ‘are you sure you want to delete?’ prompt may be an annoyance.
  • Give authority to your reps: Avoid passing the buck and give some customer service reps some goddamn authority to make some decisions. Escalate appropriately!
  • Don’t make the customer have repeat themselves: I had to face the same security questions and got given the same spiel so many times. I don’t want to repeat myself. When I’m having issues, I often have to call several times and/or speak to multiple reps to resolve my issue. So, similar to how Internet cookies work, why not just give me a short-term password or passphrase? It doesn’t have to be long-term password, maybe only valid for a couple of days. I could use this password/passphrase to authenticate myself and continue where I left off. Make sure it’s easy to pronounce and easy to remember.
  • Make calls to your customer service phoneline free Enough said. Try to ensure your customer isn’t charged for their time on the phone to fix their issues (unless there’s a sound business reason behind it, of course).

Boner Pants

bp_4
Exhibit A: Notice the uniform, transverse fold of fabric across the nether region.

There are pants that make you look like you have a boner. The construction of the fabric is such that it naturally looks as if Captain Standish is sailing at full mast.

These are called boner pants.

This affliction affects many people, including females. Please, be thoughtful in your judgment — as not all (pant wearers and witnesses alike) are aware. That is, not all tents are really pitched.

bp_3
Exhibit B

How can one determine the authenticity of such a state? One way is the ‘fold test’, which involves pinching and folding the fabric, as the moving picture below demonstrates. However, this is not a feasible nor a graceful way to demonstrate to your fellow witnesses that you are indeed in a condition of monk-like calm.

Often, it’s the excess that forms a ‘mound’. Here is the ‘fold’ test that demonstrates the lack of genuine wood (unless you happen to have a member like a tape measure…)

P.S. Boner pant wearers may be incognisant because (a) they know they aren’t aroused, so they think they have nothing to hide beneath the belt and (b) the purported inferior peripheral version in males compared to females?

An Example of Courage in Friendships

A few years ago, a few of my friends and I were planning a catchup dinner. One friend declined citing the reason that the proposed location was too far even though it suited everyone else. I complimented her courage for saying ‘no’ to meeting up at said location. Later, a friend indicated that it was odd thing for me to say because she’s our friend so she could say ‘no’. However, I find that it’s frequently tough to say ‘no’ to friends, to stand firm when the status quo is against you or your favour. This is why Neville Longbottom was awarded 10 points after all.

It takes a great deal of bravery to stand up to our enemies, but just as much to stand up to our friends.

Albus Dumbledore (J.K. Rowling) Harry Potter and the Sorcerer’s Stone

Dear Nina

Dear Nina,

You gotta be shitting me. How the fuck can you be gone? I still don’t believe it. This has got to be a cruel, cruel joke. Someone please say it’s a joke…

I don’t think any words of mine can do justice.

Here I am, breathing in air. I feel guilty that you are no longer alive as I continue to live, as the world continues to operate in seeming oblivion, when such a lovely person as yourself is no longer with us. It makes me sick. You had so much more to give and so much life ahead of you.

You were awesome. A real cool cat. And this ain’t some platitude or cliche I’m just throwing out there — you were always out doing cool things with that radiant, smiling personality of yours. Oh man, that warm, authentic smile that creased the corner of your eyes. You seriously were awesome: going on adventures, concerts, conferences, sharing beautiful music and links, making people smile and genuinely caring about others. I had a tremendous amount of respect for you: you were the president of Macquarie University Computing Society (MUCS), you were a person of integrity — a rare find — you supported charities, you took care of your body, you were a vegetarian, you were patient, understanding and compassionate. Plus you had a brilliant sense of humour — uncovering the likes of The Oatmeal and The Darkness videos. You did things because you believed them to be just and you were genuinely interested in what you were doing. You did not buckle to society’s pressures.

Just the other day I saw some old photos of you popping up on my Facebook news feed and I thought what a wonderful person you are and admired you. I realise now why it popped up in my news feed — because some of your friends were going through old photos post this tragic event.

I had plans to do the City2Surf next year with you and possibly travel together (and to think I considered joining you on this trip to Nepal…) Gah! I just can’t believe it.

You were a model person — truly and sincerely — you were a model person.

I can shed a million tears and wish for you to come back all I want, but it’s a futile effort. I do hope that I stay alive until a time machine is invented so we can meet again. It is surreal to think a person can disappear so instantaneously. But you know what? You lived your life wholeheartedly and thoroughly. I shall try to do the same friend, I shall try to do the same.

Life is fucking volatile. This tragedy has demonstrated it so plainly and viscerally. We see death everywhere — in movies, in books, in music — but we don’t internalise the fact enough.

To all those that are reading this: go forth and be awesome like Nina before you’re dead. Get all your shit in order and do what you want to do. Be responsible, take care, but do shit.

My thoughts and my deepest, deepest condolences to everyone who knew you — particularly your family and Matt. Matt…I’m so sorry dude, so sorry.

You will be sorely missed dear friend. You have enriched so many lives. Rest in peace friend, rest in peace.

I Don’t Believe in God but Maybe You Should

The story so far:
In the beginning the Universe was created.
This has made a lot of people very angry and been widely regarded as a bad move.

Douglas Adams The Restaurant at the End of the Universe

I don’t believe in God. At least the traditional, personal God: the benevolent, bearded man-in-the-sky who looks out for you, creates obstacles to challenge and test you et cetera. Or any of those classic deities e.g. Ganesh, Ahura Mazda, Odin, Jupiter.

Gods: Mazda, Ganesha, Odin and Jupiter
I wanted to make a picture with Ganesh as a hood ornament of a Mazda car with Anthony Hopkins (Odin) driving on the planet Jupiter, but alas, that was too hard and effortful.

Why? I’ve yet to come across any proper piece of evidence to believe in such a thing. Most claims of evidence are permutations of “Dude, you gotta believe me!” and “There are heaps of testimonies in this book.” Dogma. So I’ve chosen to revert to my default state of not knowing and not believing.

Because of the dogma alluded to above, sociocultural and other factors, many are simply born/raised into their faith. A boy born in the Philippines is likely to be a Roman Catholic. A girl born in Egypt is likely to be a Muslim. A goat born in Dresden is…likely to be a goat in Dresden…I digress sillily. So it seems a significant number believe simply because they were taught to.

I know a bit about dogma because I was Hindu for most of my life (yes, I know that ‘being a Hindu’ is very amorphous and vague, seeing as it has no authoritative definition.) I did things I was told to: pray to deities, don’t cut your hair or nails on specific days, abstain from eating certain foods, recite hymns as a form of worship and so on. It was no easy undertaking to turn around and let go of something that has been such a major part of you for so long. I’m often reminded of The Matrix, where Morpheus remarks to Neo: “You have to understand, most of these people are not ready to be unplugged. And many of them are so inured, so hopelessly dependent on the system, that they will fight to protect it.” It’s only natural to reject an idea that’ll show you to be a fool — but a requirement for growth, in my humble opinion.

Ask yourself this: would you be following your current religion if you had been born in a different time and place? Why don’t you believe in Ra or Ereshkigal or the Flying Spaghetti Monster? Or that Elvis is still alive?

But before you jump to the conclusion that I’m ramming my views on you unfairly, let me be clear: I am all for your right to believe in what you’d like. In fact, I’d fight to defend this right. However, I still retain the ability to think you a goofball in this particular matter if I choose to.

Incidentally, I, for one, am truly glad to be living in an age and place that affords me the right of speaking my mind without facing persecution (though, there may be some minor negative consequences.) I certainly wouldn’t have lasted long atop a Judas cradle through an inquisition.

A Case for Belief

Something has struck me though. Some truly exemplary humans I know are pious and a large part of their wondrous conduct and achievements are attributed to their belief in God. I’m constantly in admiration and awe.

Is God a Tool?

Maybe belief in a traditional God can be useful for some.

It’s like when I’m out running. I often lie to myself about how much further/longer I have to run. I pretend that I only have to run to a nearby milestone (e.g. a specific tree ahead) and concentrate on getting there. But once there, I keep on going to the next milestone. I increase the distance to the figurative carrot-on-the-stick and repeat the process over again.

This approach, while slightly silly, has proven wholly effective in helping me make progress and motivate myself. I fully know that I’m lying to myself, but I convince myself otherwise^.

If it works in making you a good and noble person, great. It certainly works for Bear Grylls, who is Christian and attributes his faith as the backbone in his life (which I initially found somewhat surprising, possibly because he struck me as a practical man, though in hindsight it shouldn’t have been surprising at all.)

Moreover, there may be other advantages of subscribing to a religion: community, a feeling of connectedness, support etc.

But with this said, many have been able to function perfectly well without needing a religious influence to provide these advantages. So, do you really need unfounded beliefs to be effective? I guess sometimes you do. But shouldn’t a delusion be recognised as a delusion, even if it is effective? I guess I’m just uncomfortable with blind faith.

Ultimately, I’ve found this piece of wisdom particularly useful in my decision to be an atheist:

Live a good life. If there are gods and they are just, then they will not care how devout you have been, but will welcome you based on the virtues you have lived by. If there are gods, but unjust, then you should not want to worship them. If there are no gods, then you will be gone, but will have lived a noble life that will live on in the memories of your loved ones.

Marcus Aurelius (Unverified) Meditations

Interesting Links and Further Reading


^ Perhaps it’s the strain of exertion that’s also a factor in making myself believe silly things?

Lessons Learned From Trading and Collecting Pokémon Action 3D Cards

My collection of Pokemon Action 3D Cards
My Collection of Pokemon Action 3D Cards

I recently unearthed some Pokémon Action 3D cards as I was doing a spot of cleaning. These are collectible, holographic cards found in various snacks like potato chips and cheese curls circa year 2000, as part of a promotion. They were all the rage in school playgrounds and much of recess was spent discussing and trading these cards. At the sound of the bell, some of us would gather around eagerly under the shade of playground trees, and collectively open up our snacks. The distinctive rustling of the shiny packets would attract crowds as we’d proudly declare a rare, new addition or lament our luck in receiving a duplicate card. “Woohoo! I got a purple Electrode! Check it out!” cried some. “Oh man! Another Hitmonlee…I already have like 3 of these. Anyone wanna trade?” cried others.

Buying into the dream marketed to us kids, I went through a mountainload of chip packets (naturally involving a lot parent pestering) and many crafty trades to amass my collection. Out of the 51 available cards, I have 50.

Included in the 50 is the rare yellow Meowth card which was only sold as part of a collectors binder/album, which in turn was sold alongside a particular newspaper on a particular weekend (if I remember correctly). I managed to convince a friend-of-a-friend who had this collectors album that trading his Meowth for some of my cards would be a good idea. At the time, I briefly thought it was somewhat shady deal, as the kid was younger than me and perhaps didn’t know the value of the Meowth card — but I didn’t go out pointing this out to him. I simply put down an offer and he simply agreed, I concluded. Overall, I was pretty chuffed that I’d managed to secure the Meowth — all without even purchasing the collectors album!

Gold/Yellow Meowth - Pokemon Action 3D Card
My Meowth Action 3D Card

Another observation that strikes me is how I am one card short of the full collection. I still remember how supremely close I was to acquiring the missing card to my collection: Koffing. I was near the afforementioned trees, where I was negotiating a deal with then-best-friend, Johnny, to trade his Koffing but I was drawn away to something else just before the trade was done (perhaps a stray tennis ball that had landed nearby?) During this period of distraction, my now-best-friend had already nabbed the Koffing from Johnny (mind you, this wasn’t to shaft me intentionally or anything sinister — I think he just wanted the Koffing too). Unfortunately, the deed had been done and I was too late. Obviously, I left disappointed that I’d missed out, but I figured that I’d get it eventually. Soon afterwards the promotion ended and these cards were no longer packaged with chips and snacks.

Koffing - Pokemon Action 3D Card
A Koffing Action 3D Card (source)

Casting my mind back to these times is a wistful experience, of course. To think that I used to covet such trivial cards is silly — but a dream is nevertheless, a dream. It is also humbling, because it reveals not only how far I have come since those carefree times and how things have changed, but also how these moments have shaped me. It’s also informative and amusing to reflect on the way certain moments unfolded and cling to your brain despite decades of separation.

Ruining Jokes

Weeks ago, I had an interaction with a friend that went like this:

Hey, what does Snoop Dogg use an umbrella for?

Friend

Fo’ drizzle?

Me

Yep

Friend

Just as I was saying the words “Fo’ drizzle?”, I realised that I could be right (possibly because I’ve heard the joke before, but I didn’t know for sure). Should I have stopped myself? Should I have pretended that I didn’t know the joke for the sake of my friend’s benefit? Hmm.

Express and Node.js for Djangonauts: A Tutorial on Building a Polls App, Part 2

This part continues from where we left off in part 1.

This part will cover part 3 of the Django tutorial. Why? Because part 2 of the Django tutorial covers the admin interface Django is able to generate for you. Express has no equivalent tool that is as comprehensive or as feature complete as Django’s admin interface. There are some projects addressing this issue, but they’re still seedlings: http://stackoverflow.com/a/11965061/977931

A general note before we begin

You may need to restart the Node app/server after making changes to code. Just stop the server if it’s already running (using Ctrl + C) and then start it up again (e.g. using node app).

The same context and prerequisites apply from the first part of the tutorial.

Code on Github

You can find the code used in this tutorial on Github under the ‘part-2’ tag:

View Code for Part 2 on Github

Writing our first ‘view’

Open the app.js file and add the following code just prior to the http.createServer call at the end:

app.js
1
2
3
function pollIndex(req, res) {
  res.send("Hello, world. You're at the poll index.");
}

This is the simplest ‘view’ possible in Express. Views in Express are conceptually similar to Django views — they’re both functions that take in a request (req) and return a response (res). For convenience, I’ll call such functions a ‘view’ as well, even though it’s not entirely accurate in Node.js/JavaScript/Express parlance.

To call this Express view, we need to map it to a URL, which we can do using get(). Add this code after the function pollIndex() (that we just added):

app.js
1
app.get('/polls', pollIndex);

This code defines that whenever a HTTP GET request is received at the location ‘/polls’, let the view pollIndex() deal with it. You can also use the different HTTP verbs to route requests e.g. app.post('/someplace', someView);, but I’m getting ahead of myself.

You can find more information on routing (including using regexes to capture URL bits as in Django) in the Express API reference for application routing.

Now, if you save this file and run node app and visit http://localhost:3000/polls in your browser, you should see the message “Hello, world. You’re at the poll index.”

Writing more views

Let’s add more views to app.js! These views are different because they use a parameter from the request:

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function detail(req, res) {
  poll_id = req.params.poll_id
  res.send("You're looking at poll " + poll);
}

function results(req, res) {
  poll_id = req.params.poll_id
  res.send("You're looking at the results of poll " + poll_id);
}

function vote(req, res) {
  poll_id = req.params.poll_id
  res.send("You're voting on poll " +  poll_id);
}

In Django, these parameters would be passed into the view as an argument. For example:

views.py
1
2
3
4
5
6
7
8
def detail(request, poll_id):
    return HttpResponse("You're looking at poll %s." % poll_id)

def results(request, poll_id):
    return HttpResponse("You're looking at the results of poll %s." % poll_id)

def vote(request, poll_id):
    return HttpResponse("You're voting on poll %s." % poll_id)

Anyhoo, let’s update our routes in app.js from this:

app.js
1
app.get('/polls', pollIndex);

to this:

app.js
1
2
3
4
5
app.get('/polls', pollIndex);
app.get('/polls/:poll_id', detail);
app.get('/polls/:poll_id/results', results);
app.get('/polls/:poll_id/vote', vote);
app.post('/polls/:poll_id/vote', vote);

Load /polls/22 in your browser and it’ll call the detail() function and display whatever ID you provide in the URL. Similarly, try /polls/22/results and /polls/22/vote too, which will bring up the placeholder results and voting pages.

Note also the explicit post specification in the routes for voting via the app.post() invocation, which will eventually be used for capturing form submissions. In Django, the view typically deals with POST submissions (unless some Django middleware intercepts it). Express gives you a finer control at the routing level about directing HTTP requests to different views.

The URLs/routes in the config are loaded in order, just as in Django. That is, ‘/polls’ would be tried before ‘/polls/44/results’.

You can also use regular expressions if you need or choose to. Consider an example that shows shoes within a certain size range:

app.js
1
2
3
4
5
6
7
8
// Show shoes between certain sizes
// Example valid URL 1: /items/shoes/sizes/8-13
// Example valid URL 2: /items/shoes/sizes/10-16
app.get(/^\/items\/shoes\/sizes\/(\d{0,2})-(\d{1,2})$/, function(req, res){
  var min_size = req.params[0];
  var max_size = req.params[1];
  res.send('Showing shoes between sizes ' + min_size + ' to ' + max_size);
});

Write views that actually do something

A view is supposed to returned a response given the request. Once we reach the view level, what gets done is up to you. Like in Django, you can pull stuff out of a database, initiate rocket launchers, render a CSV file — whatever. Incidentally, I wonder if there is a web interface for nuke codes that the President of the United States controls…

Now, let’s grab the latest 5 polls from the database, separated by commas, according to the publication date:

1. Ensure var pollsdb = require('./models/db') and mongoose = require('mongoose') are in the dependencies near the top of app.js. If they’re not there, add them. So it might look something like this:

app.js
1
2
3
4
5
6
7
8
9
10
11
/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , mongoose = require('mongoose')
  , pollsdb = require('./models/db')
  , path = require('path');

2. Subsequent to (i.e. after) the above, add the following to app.js:

app.js
1
2
3
4
5
6
7
8
// Configure appropriately
mongoose.connect('mongodb://localhost/pollsdb');

var conn = mongoose.connection
conn.on('error', console.error.bind(console, 'Database connection error:'));
conn.once('open', function callback () { console.log("Hey handsome. Database connected.") })

var Poll = conn.model("Poll", pollsdb.pollSchema)

3. Update the pollIndex() view:

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function pollIndex(req, res) {
  Poll.find().sort('-pubDate').limit(5).exec(function(err, polls) {
    if (err)
      // Pass it along to the next error-handling middleware to deal with
      next("Uh oh! Something went wrong when fetching polls from the database.");
    else if (polls) {
      console.log(polls);
      var questions = new Array();
      for (var i = 0; i < polls.length; i++) {
        questions.push(polls[i].question);
      }
      // Make a comma-separated string of questions
      result = questions.join(", ");
      console.log(result);
      res.send(result);
    }
    else {
      res.send("No polls found :(");
    }
  });
}

Firing up http://localhost:3000/polls in your browser should give you a comma-separated list of the last 5 questions.

Introducting templates

The result string is hardcoded into the response, which is no good! Let’s use templates instead to make things more maintainable.

By default, Jade is used for templating. You can use different engines like Haml or EJS if you’d like. When creating an Express skeleton project, you can choose from pool of view engines (like EJS) to get started quickly. Run express --help for more information.

And if you’re feeling a bit ‘homesick’, there are also some Jinja implementations for Node: Swig, Jinja-like JS templating language. I haven’t used these, so your mileage may vary.

The directory where templates are stored and template engine used is specified in app.js with:

app.js
1
2
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');

Anyway, create a file called pollIndex.jade in the views folder and put the following code:

pollIndex.jade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//- Example of template inheritance
//- A default layout (layout.jade) was created with the skeleton project
extends layout

block content
  h1= title
  if latest_poll_list
    ul
      //- Loop through the list of polls.
      //- Akin to the for template tag in the Django templating language.
      each poll in latest_poll_list
        li
          //- Jade makes it possible to write inline JavaScript code in templates.
          //- Anything after an equals sign ('=') is 'buffered code'
          //- which outputs the result of evaluating the JavaScript expression.
          //- Refer to the Jade docs: http://jade-lang.com/reference/
          //- 
          //- Here, we create a link to the poll.
          //- Note the dot notation used, similar to Django.
          a(href='/polls/' + poll._id)= poll.question
  else
    p No Polls Found :(

and update the res.send(result); line in the pollIndex() view to:

app.js
1
res.render('pollIndex', { title: "Latest Polls", latest_poll_list: polls });

Crank up http://localhost:3000/polls in your browser and you should see a list of polls.

A list of latest polls

The code to render and return a template is alike to Django. Here’s how you might do it in Django:

views.py
1
return render(request, 'polls/index.html', {'latest_poll_list': latest_poll_list})

The detail() view

Let’s work on the poll detail view, which shows the poll question. Update the detail() view as follows:

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function detail(req, res, next) {
  var poll_id = req.params.poll_id;
  // findById() is akin to Django's get(pk=poll_id)
  Poll.findById(poll_id).exec(function(err, poll) {
    if (err) {
      // Pass it along to the next error-handling middleware to deal with.
      // It should return a 500 Internal Server Error
      next("Uh oh! Something went wrong when fetching a poll from the database.");
    }
    else if (poll) {
      res.render('pollDetail', { poll: poll });
    }
    else {
      // Pass it along to the next middleware component
      // as there was nothing found (should deliver a 404)
      next();
    }
  });
}

Also create a simple template pollDetail.jade for the time being:

404.jade
1
2
3
4
5
6
7
8
9
10
11
12
13
//- Example of template inheritance
//- A default layout (layout.jade) was created with the skeleton project
extends layout

block content
  if poll
    h1= "Poll: " + poll.question
    ul
    each choice in poll.choices
      //- Equivalent to `li= choice.choiceText`
      li #{choice.choiceText}
  else
    p No poll found :(
An example poll detail page

Raising 404 errors

To send a 404 status back, you simply set the HTTP in the response e.g. res.status(404). Here’s what the Express documentation has to say about handling 404s (from the FAQ):

In Express 404s are not the result of an error, thus the error-handler middleware will not capture 404s, this is because a 404 is simply the absence of additional work to do, in other words Express has executed all middleware / routes and found that none of them responded. All you need to do is add a middleware at the very bottom below all the others to handle a 404: app.use(function(req, res, next){ res.send(404, ‘Sorry cant find that!’); });

So, based on the above advice, let’s add a 404 handler thingy — but one that’s fancier and has the site’s look-and-feel.

Add this code somewhere after the routing (app.use(app.router);) and static files (app.use(express.static(path.join(__dirname, 'public')));) middleware usage declarations:

app.js
1
2
3
4
5
// Error Handling
// As per "How do you handle 404s?" on http://expressjs.com/faq.html
app.use(function(req, res){
  res.status(404).render('404');
});

and create this template file 404.jade in the views folder:

404.jade
1
2
3
4
5
extends layout

block content
  h1 404: Page Not Found
  p Sorry, can't find the page you're after :(

If you now visit a non-existent page like http://localhost:3000/polls/boogboo, you should see a nice 404 page (unless you have a poll with the ID of ‘boogaboo’!):

An example page not found page

Unlike Django where you say “I tried to find this poll but couldn’t, so have a 404 bro,” you say “I tried to find this poll but couldn’t, so I’ll pass it on to the next middleware dude — he’ll deal with it.”

Removing hardcoded URLs in templates

In our pollIndex.jade template, we have hardcoded the URL like so:

404.jade
1
a(href='/polls/' + poll._id)= poll.question

In Django, you can use the {&#123; url &#125;} template tag to decouple URLs. There is no counterpart to this Express by default, however you can implement something close to it. But cool URIs shouldn’t change and redirects should be installed if they do.

Neatening things up

By now, you should’ve noticed how cluttered and full app.js is getting. Let’s chunk things things up a bit, shall we?

Let’s move the views into its own file. I’ve decided to put these views into the routes/index.js file (which was generated with the app skeleton was generated). This effectively becomes a views.py equivalent:

routes/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
var mongoose = require('mongoose');
var pollsdb = require('../models/db');

mongoose.connect('mongodb://localhost/pollsdb');

var conn = mongoose.connection
conn.on('error', console.error.bind(console, 'Database connection error:'));
conn.once('open', function callback () { console.log("Hey handsome. Database connected.") })

var Poll = conn.model("Poll", pollsdb.pollSchema)


/*
 * GET home page.
 */

exports.index = function(req, res){
  res.render('index', { title: 'Express' });
};

exports.pollIndex = function(req, res, next) {
  Poll.find().sort('-pubDate').limit(5).exec(function(err, polls) {
    if (err)
      // Pass it along to the next error-handling middleware to deal with
      next("Uh oh! Something went wrong when fetching polls from the database.");
    else {
      var questions = new Array();
      for (var i = 0; i < polls.length; i++) {
        questions.push(polls[i].question);
      }
      // Make a comma-separated string of questions
      result = questions.join(", ");
      //res.send(result);
      res.render('pollIndex', { title: "Latest Polls", latest_poll_list: polls });
    }
  });
}

exports.detail = function(req, res, next) {
  var poll_id = req.params.poll_id;
  // Check if the ID provided is an ObjectID used by MongoDB (used by default)
  // Could also do this matching in the routes specification
  // Refer to: http://stackoverflow.com/a/14942113/977931
  if (poll_id.match(/^[0-9a-fA-F]{24}$/)) {
    // findById() is akin to Django's get(pk=poll_id)
    Poll.findById(poll_id).exec(function(err, poll) {
      if (err) {
        console.log(err);
        // Return a 500 Internal Server Error
        next("Uh oh! Something went wrong when fetching a poll from the database.");
      }
      else if (poll) {
        res.render('pollDetail', { poll: poll });
      }
      else {
        // Pass it along, nothing found (should deliver a 404)
        next();
      }
    });
  }
  else
    // Pass it along, nothing found (should deliver a 404)
    next();
}

exports.results = function(req, res) {
    poll_id = req.params.poll_id
    res.send("You're looking at the results of poll " + poll_id);
}

exports.vote = function(req, res) {
    poll_id = req.params.poll_id
    res.send("You're voting on poll " +  poll_id);
}

Note how I’ve moved the relevant database connection configuration into the top of routes/index.js.

The app.js file would look like this now:

app.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
/**
 * Module dependencies.
 */

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , mongoose = require('mongoose')
  , pollsdb = require('./models/db')
  , path = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// Error Handling
// As per "How do you handle 404s?" on http://expressjs.com/faq.html
app.use(function(req, res){
  res.status(404).render('404');
});

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

app.get('/polls', routes.pollIndex);
app.get('/polls/:poll_id', routes.detail);
app.get('/polls/:poll_id/results', routes.results);
app.get('/polls/:poll_id/vote', routes.vote);
app.post('/polls/:poll_id/vote', routes.vote);

// Show shoes between certain sizes
// Example valid URL 1: /items/shoes/sizes/8-13
// Example valid URL 2: /items/shoes/sizes/10-13
app.get(/^\/items\/shoes\/sizes\/(\d{0,2})-(\d{1,2})$/, function(req, res){
  var min_size = req.params[0];
  var max_size = req.params[1];
  res.send('Showing shoes between sizes ' + min_size + ' to ' + max_size);
});

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

View the full source on Github

You can check out the final source code at this stage on Github under the ‘part-2’ tag:

View Code for Part 2 on Github

To be continued

This brings part 2 of the tutorial to an end. Stay tuned for the next part which will deal with simple form processing. Follow me on Twitter or subscribe to the RSS feed for updates.

If you have any questions, leave them in the comments below!

How I Keep Organised

You’re a busy person. You’ve got things to do. How do you manage all this?

Well, let me share with you my approach. Maybe it might be helpful to you. I’d also like to hear how you operate — so leave a comment below or tweet me your thoughts!

I find that there’s too much stuff going on. So I’ve adopted the attitude that if it’s not important, then it won’t be in the forefront of my brain. But how do you tell what is actually important? Pretty much anything you’re ready to respond with ‘HELL YEAH!’ — as popularised by the venerable Derek Sivers.

Anyway, this is my current set up:

Google Calendar

I keep myself organised mainly through Google Calendar. Any event or reminder should theoretically end up as an event on Google Calendar (and most do).

In the past I’ve kept a diary, but I didn’t always have it handy when I needed it. Plus I’d forget to do things if I didn’t diligently keep abreast with my affairs.

Because I have my smartphone (currently a Samsung Google Nexus S) more often than my diary, it’s easy to record events and todos before they vanish into the ether and return to the fore at the most dreadfully inapt time — like a pack of clowns pouring out of a tiny red automobile in the midst of a funeral.

It doesn’t have to be Google Calendar, it could be any of the alternative calendars around. I picked Google Calendar because of its integration with Gmail and also the apps available on iOS, Android and other OSs.

And since I have such a woeful memory, I’ve also specified that the default reminders pester me several times before the event itself. This way, it’s assuredly difficult for me to forget something.

Google Calendar Reminders

I do all this because I once forgot about a team meeting I organised as team leader. Egg on my chocolate face.

And since I have such a woeful memory, I’ve also specified that the default reminders pester me several times before the event itself. This way, it’s assuredly difficult for me to forget something.

Google Calendar Reminders

You might also be looking at my previous comment of “if it’s not important, then it won’t be in the forefront of my brain” and wonder “then why all these reminders?” Well, it’s because they are important and I’m forgetful!

And since I have such a woeful memory, I’ve also specified that the default reminders pester me several times before the event itself. This way, it’s assuredly difficult for me to forget something.

Google Calendar Reminders

Email

My email serves as my repository of things to do.

I label my emails: ‘Todo’, ‘Follow Up’ and ‘Complete’

  • Todo items get labelled as such. These are things I need to do (as the name implies).
  • Follow Up items are things that need to be looked at later (but without a specific time). I glance through this list every so often.
  • Complete are todo items that I’ve completed. It’s not strictly needed because I archive completed items, but it can help when reviewing things you’ve done recently.

Incidentally, I find the drag-and-drop to label feature of Gmail super convenient.

I’ve tried todo list and Getting Things Done (GTD) tools like Astrid and Wunderlist and while they’re gorgeous, I found the effort needed to turn emails into todo items too much — especially since most of my todo items are contained within emails.

To create todos, I simply email myself. I’ve set up a filter so that any email that begins with ‘TODO’ in the subject automatically gets labelled with ‘Todo’.

Having said all this, I will admit I’m much more relaxed when it comes to my personal inbox — I’m stricter when it comes to my work inbox.

Other Email Tips

This applies mainly to Gmail, but can be generalised to other/regular email providers:

  • Ruthlessly automate with filters (I quite like the ‘Archive’/skip inbox feature so I can refer to things I may be interested in if needed without cluttering my inbox)
  • Enable and learn keyboard shortcuts: Gmail keyboard shortcuts
  • Explore the plugin ecosystem (e.g. Thunderbird add-ons, Gmail Labs features). I’ve enabled a few Gmail Labs features including ‘Undo Send’, which queues messages to be sent after hitting ‘Send’, so that you can ‘undo’ your email.
  • Tweak your settings to your needs
  • Unsubscribe from useless mailing lists
  • Use search to find things!
  • Label your emails appropriately

Writing on Yourself

Another reminder approach I’ve taken (and still sporadically take), is to draw or write on myself as way of reminding myself. A simple shape like a triangle on the back of your hand serves as a good reminder when you’re washing your hands or looking at your phone. Sometimes I draw on my forearm or elsewhere to prevent the ink from washing off quickly.

The 'Remembrall' method of drawing on your hands as a reminder.
The ‘Remembrall’ method of drawing on your hands as a reminder.
Writing on your forehand to prevent it washing off so quickly.
Writing on your forehand to prevent it washing off so quickly.

Evernote

Apart from that, I also use Evernote to record notes and thoughts. Anything from observations to a photo of the menu at the local fish-and-chip shop. I love how everything is kept in sync across my devices.


So there you have it. A quick run-down of my getting-things-done approach.