Database Storage![[next]](../../img/ltp/arrow-next.gif)
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.
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. |
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.
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.)
|
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.
If you want to store and manipulate collections of similar StorableObjects, which is extremely common in a web app, use a StorableCollection.