[prev]Database Storage[next]

Let's talk about how data is stored persistently on the web. When you're reading a friend's blog or livejournal, for example, and you leave a comment, the website has to store the data somehow, so that your comment can be shown to everyone who comes to that page from then on.

The blog or journal posts themselves also need to be stored, as well as your username, profile picture, and so on. This kind of permanent, structured storage is the job of a database.

You've probably noticed that variables like var x, though they store information, store it very temporarily. Once the user goes to a different page of your app, your code runs again, and the variables have their values reset. AppJet's "storage" library, on the other hand, gives you database-style persistent storage, but in a way that's designed to be as easy-to-use as normal variables and objects.

Example: Hit Counter

Here's an easy way to count the "hits" your app gets! After you run this example app, press the "run again" button repeatedly and watch the number go up.

 
1
2
3
4
5
6
7
8
9
import("storage");

if (! storage.counter) {
  storage.counter = 0;
}

storage.counter++;

printp(storage.counter, " hits.");

Click to run the code.

A hit is when a user requests a page from your app.

It's one measure of how popular an app is; other measures that are used on the web include counting visits to a site (where clicking around your app is counted as a single visit), counting activity in some time period, or counting the number of people who use your app.

When you import the AppJet "storage" library using import("storage"), you get access to an object called storage, a special object that's permanently persisted.

Once you set a property of storage to a string or number, it has the new value from then on, for everyone who accesses your app.

Exercise: Clear the counter. Reset the counter by temporarily disabling the if statement, but not the code inside it that sets the counter to 0. One common way to do this is to add // at the beginning of line 3 and line 5; you'll see those two lines turn red, showing that they have been "commented out", or disabled. Run the app, see that it shows a count of 1, and then re-enable the code.

Exercise: Store a string. Change the app so that instead of adding one to a stored number, it adds new characters to a stored string. The first time the app is viewed, it should show "x", then "x x", then "x x x", and so on.

Storable Objects

A StorableObject is a lot like a regular object, but it has some restrictions. For example, it can't have a function or an array as a property.

The object storage is itself a StorableObject. A particularly useful thing about StorableObjects like storage is that they can have properties that are other StorableObjects. (If you assign a regular object as a property, the regular object will be copied and converted into a StorableObject before assigning it.)

Example: Which Napoleon is Better?

 
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
import("storage");

if (! storage.pollData) {
  storage.pollData = { dynamite: 0, bonaparte: 0 };
}
var data = storage.pollData;

if (request.path == '/bonaparte') {
  data.bonaparte++;
}
if (request.path == '/dynamite') {
  data.dynamite++;
}
if (request.path != '/') {
  response.redirect('/');
}

print(html("<h3>Which Napoleon is better?</h3>"));

printp(link('/bonaparte',
    html(image('http://i38.tinypic.com/ka32i0.gif'),
        " Bonaparte")),
    " (", data.bonaparte, " votes)");
printp(link('/dynamite',
    html(image('http://i37.tinypic.com/24nfms8.gif'),
        " Dynamite")),
    " (", data.dynamite, " votes)");

Click to run the code.

This app creates a StorableObject assigned to storage.pollData. Properties of the object are persisted, just like properties of the root storage object.

The paths "/bonaparte" and "/dynamite" represent the actions of voting for one of the Napoleons. Note that when these paths are handled, the command response.redirect("/") is used. This command stops execution of the request (i.e. exits the program) and instead of showing the user a web page, tells the user's browser to "redirect" to a different path.

We use a redirect in this case because leaving the user's browser at a path that performs an action would be awkward; if the user clicked "reload" another vote would be counted!

Challenge: Keep people from voting more than once. If you're going to release a poll like this on the Internet, you don't want people to be able to game it (i.e. cheat) just by clicking more than once. The easiest and most fool-proof way to prevent this is to remember the users who have voted, identified by what computer they're connecting from, and give an error when they try to vote again.

As a test, add the code printp(request.clientAddr); to the bottom of the program and run it again. The address you see is called the IP address of your computer, as seen by the code. An IP address is like a "return address" of an web request.

To have the app remember what IP addresses have been used to vote, first add code near the top of the program to create an empty StorableObject at storage.voterAddresses when it doesn't exist already (basing your code on how pollData is created). We'll give this object a property for each voter.

Next, Assign the value of request.clientAddr to a variable called ip. The remaining changes you need to make are to NOT accept votes if (storage.voterAddresses[ip]). When a user votes, make sure to set storage.voterAddresses[ip] = true.

Do IP addresses really identify "users" accurately?

A perfect technique for preventing cheating in a poll would reject people who vote more than once, while never incorrectly turning someone away. Obviously the IP address technique isn't perfect, right off the bat, because what if multiple people in a household share a computer? They only get one vote between them.

More subtly, though, and this gets to be a pretty advanced topic, your computer doesn't usually have a unique IP address when you connect to the Internet from home, school, or work. If you're using a cable modem at home, all the computers in your house probably share an IP address, which is unique to your house at any given time, but may be reassigned by the cable company once in a while. At school or work, it may be that all the computers in the building are channeled through a single IP address.

Nevertheless, though this technique may wrongly turn votes away sometimes, it works well on the whole because it's difficult to game. Cheating requires access to a lot of different IP addresses. The easiest (legal) way for someone to pull this off is to mobilize a community of people on the Internet to descend on your poll and vote.

Storable Collections

If you want to store and manipulate collections of similar StorableObjects, which is extremely common in a web app, use a StorableCollection.

Previous Lesson: Life of a Request Next Lesson: More Lessons