By Evan Miller
DRAFT: September 7, 2010 (changes)
11/5/2011 NOTE: The following tutorial is out of date. Please contact me if you are interested in making a new tutorial for the 0.6.x series. -EMM
I wrote a web framework for Erlang called Chicago Boss. This guide will show you how to use it to write a simple web application, complete with logins and an RSS feed.
“Well, what’s in it for me?” —You
Chicago Boss gives you a high-performance database that scales, an advanced ORM, fast Django templates, clean pattern-matching controllers, automatic code reloading, auto-generated documentation for your models, a clunky but very useful admin interface, and an innovative functional test framework, all right out of the box. If you choose to use Boss, your development cycles will be fast, your code will be clean, and your servers will be grateful.
If you’re new to Erlang and curious about what makes Chicago Boss fast, check out Chicago Boss’s Big Secret. If you don’t care about whys and just want to get behind the wheel, read on. If you have any questions or find any errors, feel free to check out the mailing list or just send me an email personally, [email protected].
Let’s get started!
“You leave the thinkin’ to me.” —Boss
Here’s the roadmap, pilgrim:
- Learning Erlang
- Installing Erlang
- My First Boss
- Essential Tricks: /admin and /doc
- Model, View and Controller
- Associations: belongs_to and has
- How To Implement Log-ins
- How To Actually Use Log-In Information
- How To Validate Input
- How To Implement A Log-Out
- How To Implement an RSS feed
Learn Erlang if you don’t know it. You can try to pick up on Erlang syntax by reading the examples in the rest of this article, but if you want to build things, you’ll need to get acquainted with a good Erlang reference. I recommend the book Programming Erlang by Joe Armstrong. If you buy the PDF, you can even start reading it today. Here, I’ll even tell you the parts you can skip if you just want to start building things with Boss as soon as possible:
Read: Chapter 2, Chapter 3 (skip 3.9 Records), Chapter 5 (skip 5.2 Binaries and 5.3 Bit Syntax).
Those 3 chapters have 99% of what you need. Read Chapter 4 (Exceptions) once you’ve built a few things and need extra robustness. But otherwise, you don’t need to worry about compiling, concurrency, the process model, server loops, or any of the other things Erlang is celebrated for. Boss takes care of it.
An alternative book is free on the web: Learn You Some Erlang For Great Good! I haven’t read it but it looks entertaining.
Chapter 2 of the Armstrong book explains how to download and install Erlang. Here’s the gist of it:
Linux:
sudo apt-get install erlang
Mac:
Use MacPorts (using the +ssl
option), or:
tar xzf otp_src_R13B04.tar.gz
cd otp_src_R13B04
./configure
make
sudo make install
Windows:
Chicago Boss hasn’t actually been tested on Windows, but you can be the first! You can install Erlang by downloading a binary from the Erlang download page.
For this tutorial, we’ll use an in-memory database, so there’s nothing to install. When you want to get serious, check out Appendix A: Meet The Tyrant.
We’re finally ready to have some fun. First, download Chicago Boss, unpack, and cd:
tar xzf ChicagoBoss-0.4.5.tar.gz
cd ChicagoBoss-0.4.5
The way things are currently set up, there’s actually nothing to install. You’ll do all your work right in the source directory. To upgrade Boss you’ll just copy over a few files, and to start a new project, you’ll just download a fresh copy of Boss.
Boss ships with a little “Hello, world!” app. Let’s see if it works:
./START-DEV.SH
There will be a lot of PROGRESS REPORTs flying by on your terminal but hopefully everything is OK. Now point your web browser to:
http://localhost:8001/
(Obviously, if you’re running Boss on another machine, change localhost to the name of that machine.)
You should get a friendly little greeting from the Boss, complete with Gothic finger tattoos.
The terminal with the PROGRESS REPORTs is actually a shell that has direct access to the server. In this shell, you can type in any Erlang command, interact with the database, and use the full Boss API. It’s pretty convenient, and it’s a good place to learn a few commands.
Let’s try a few. The greeting you see in the web browser actually comes from the database. Let’s find it. That’s right, we’re about to type in the shell with all the PROGRESS REPORTs. Just trust me.
> Greetings = boss_db:find(greeting, []).
That command searches the database for greetings that match the search criteria. In the second argument we’ve specified an empty list of search criteria, so it should search for all greetings. The command should return something like this:
[{greeting, “greeting-1”, “Boss says Hello!”}]
What you see there is a list with a single BossRecord. A BossRecord looks like an ordinary tuple, but it has special powers. First let’s get the BossRecord out of the list:
{greeting,“greeting-1”,“Boss says Hello!”}
Now let’s invoke some powers:
[id, greeting_text]
The function attribute_names returns a list of the BossRecord’s attributes. These attributes are an essential aspect of programming with Chicago Boss; think of them as database columns or text fields. We will define a list of attributes for every data model in our project. Here we see that the greeting model only has two attributes (id and greeting_text), and we can access them with individual function calls like this:
“Boss says Hello!”
> Greeting:id().
“greeting-1”
The id attribute is special. It belongs to all BossRecords and its values are unique. We’ll see that IDs are automatically generated and are always prefixed with the object type (e.g. “greeting-”). It’s not that important, but the ID number is also unique, so that if there’s a greeting-1, there will not be a user-1, and if there’s a user-42, there will not be a greeting-42.
Anyway, BossRecords also come with setter functions. Recall that Erlang is a single-assignment language, so if we change the BossRecord, we need to assign it to a new variable. Let’s try changing the message:
> NewGreeting = Greeting:greeting_text(“Do I look like Santa Claus?”).
{greeting,“greeting-1”,
“Do I look like Santa Claus?”}
Now go back to your web browser and refresh. Do you see the new message?
You shouldn’t. We still need to save the changes we’ve made to the database. Saving can be accomplished with the save() function:
{ok, {greeting,“greeting-2”, “Do I look like Santa Claus?”}}
Now go back to your web browser and refresh. Tada!
Next, let’s add another greeting to the database. We use the greeting:new/2 function to create a greeting:
{greeting, id, “Where’s mine?”}
Recall that the greeting model has two attributes: id and greeting_text. The new function is always invoked with a value for each attribute: no more, no less. The attribute order is important! It’s defined in the model file, which we’ll get to in a few minutes. For now, let’s make sure we got the order right. Check the id:
id
And the greeting_text:
“Where’s mine?”
Looks OK. Notice that we passed in the atom id instead of an ID string. That tells Boss that we want the ID to be generated for us. The ID will be generated as soon as we save the BossRecord:
{ok, {greeting, “greeting-2”, “Where’s mine?”}}
The ID is always in the second position, but we can access it with the getter function too:
“greeting-2”
Since the new greeting has an ID, we know for sure it’s in the database. Let’s prove it:
[{greeting, “greeting-1”, “Do I look like Santa Claus?”},
{greeting, “greeting-2”, “Where’s mine?”}]
Now we’re rolling! If you go back to your web browser and refresh a few times, you should see both messages appear (the page just picks one at random each page load).
So far we’ve been pulling things out of the database by executing searches. But if we know the ID, we can just pull that one record. Let’s try it, just for kicks:
{greeting, “greeting-1”, “Do I look like Santa Claus?”}
That function (boss_db:find/1) is probably the function you’ll use more than any other in Chicago Boss programming, so get to know it well. Fortunately it’s pretty easy: just pass in the ID as a string. If you want to make sure the ID corresponds to the model you think it does, just use the boss_db:type/1 function:
greeting
That comes in handy for defensive programming. (The type function isn’t completely trivial, it returns undefined if there’s no record with that ID in the database.)
Hey, did you realize that we’ve already covered the C as in Create, the R as in Read, and the U as in Update of CRUD? All that’s left is D as in Delete! Let’s kill that greeting that Boss shipped with:
ok
And to prove it’s not in the database any longer:
[{greeting, “greeting-2”, “Where’s mine?”}]
A shorter way to check how many items are in the database is the boss_db:count/1 function. It takes the model type as the only argument:
1
There’s another version, boss_db:count/2, which takes a list of search criteria as the second argument. count performs fewer database queries than find, but with Chicago Boss, this difference isn’t a big deal.
Before we start writing a program, I’m going to let you in on a couple of tricks that will help you along the way.
The first is the /admin interface. /admin lets you do the same things we were doing in the shell, but through a web interface. Point your browser to
http://localhost:8001/admin
Click on “greeting” to check it out. With /admin you can
- See a list of records in the database
- View an individual record and all its associated records
- Create a new record
- Delete a record
The interface is helpful for debugging and is also helpful if you don’t feel like writing an interface for features that only site administrators see. The /admin interface is pretty clunky at the moment, but if you want to improve it, you will earn the accolades of Chicago Boss developers everywhere!
By default, /admin is accessible only from the local machine. If you want to change that, you will have to modify the logic at the top of ADMIN/admin_controller.erl. For now, it should be fine as is.
The second trick to know is called /doc. Remember how I said that BossRecords are parameterized modules with special powers? Unlike regular Erlang modules, they get compiled with a lot of extra features that you don’t have to write yourself: getter functions, setter functions, the save/0 function, and so on. (Note that the standard Erlang compiler doesn’t support inheritance; these add-on functions are a huge win for Chicago Boss and set it apart from other Erlang frameworks.)
To see all of the generated functions that your model has (plus any functions you wrote), just point your browser to
http://localhost:8001/doc/MODELNAME
(e.g., /doc/greeting)
Not only will you see documentation for all functions, but you’ll also find the all-important ordered attribute list in big letters at the top. You’ll probably consult that list whenever you call the new function, so get used to typing /doc into your browser.
With these tools in hand, you should be itching to hammer out some code.
In the interest of simplicity, I’m going to show you how to build a simple app that shows a list of records and displays an individual record. We’ll cheat and use the /admin interface to populate the database, but by the end of this guide you should be able to write your own pages for creating, updating, and deleting records, complete with user authentication and input validation. Heck we’ll even make an RSS feed before the evening is over. Let’s get started!
You should have a terminal open with the active server shell. Keep it open, and use another terminal (or your favorite text editor) to edit text files. If you accidentally closed the shell just type in ./START-DEV.SH again in the ChicagoBoss directory.
The first thing we’ll do is create a model file. Model files will be turned into those mystical BossRecords that make Boss programming such a breeze. In true Chicago style, our example app will be a database of voters. Open up a new file called model/voter.erl, and type these lines into it:
-module(voter, [Id, FirstName, LastName, Address, Notes]). -compile(export_all).
That’s all we need for now. Save and close. Point your browser to /doc to see what we created:
http://localhost:8001/doc/voter
You’ll see the parameter list we defined in bold letters at the top. Note that Id always has to be the first parameter.
Also note that we already have getter and setter functions called id, first_name, last_name, address, and notes. Chicago Boss converts each CamelCase parameter name to its underscored equivalent for these attribute functions.
If you choose to go off and write your own functions inside the model file, they’ll have access to all of these functions, and they’ll also have access to the parameter variables directly (Id, FirstName, and so forth). For now, we don’t need to write our own functions.
Next we want to populate the database with some voters. Point your browser to
http://localhost:8001/admin/model/voter
Click the button labeled “+ New voter” and fill out some dummy values, and then click “Create voter.” It should show you a page like this:
Click “Back to the voter list” and repeat until you have two or three voters in the database.
Now let’s build a little interface of our own. Create a directory in View called voter, and open up a new file called view/voter/list.html.
This file will be a template written in the Django Template Language. Let’s put a dumb little list into our template:
<ol> {% for voter in voters %} <li>{{ voter.first_name }} {{ voter.last_name }} - {{ voter.address }}</li> {% endfor %} </ol>
We’re not winning any design awards, but hey. Note that variables in the template have complete access to BossRecord attributes with the simple dot notation. Under the hood it’s calling the getter functions, so we need to use the underscore form rather than the CamelCase names. Anyhow, save and close.
Now all we need is some glue code: the controller. Open up a new file called controller/voter_controller.erl and put the following lines into it:
-module(voter_controller, [Req]). -compile(export_all). list('GET', []) -> Voters = boss_db:find(voter, []), {ok, [{voters, Voters}]}.
Let’s talk about each of these lines in order:
-module(voter_controller, [Req]).
Controllers are parameterized modules that take a single parameter called Req. Req is a SimpleBridge HTTP Request Object that gives us access to useful stuff like POST parameters, cookies, headers, etc. We don’t need Req in this example but it will come in mega-handy later on.
-compile(export_all).
(So Chicago Boss can actually access our controller functions.)
list('GET', []) ->
Each action is associated with a function of the same name. The function takes two arguments (or three, if you choose to implement log-ins, see below). The first argument is the HTTP request method, passed as an atom. As we’ll see later on, this is especially powerful in conjunction with Erlang’s pattern-matching. The second argument is a list of tokens in the URL beyond the controller name and action name; this is how we get “pretty” URLs without needing a separate URL routing file. This token list is also powerful in conjunction with Erlang’s pattern-matching, but for now, the token list is empty.
Voters = boss_db:find(voter, []),
Look familiar? This is exactly what we did in the Chicago Boss shell.
{ok, [{voters, Voters}]}.
To render the associated template, the controller function needs to return {ok, Variables}, where Variables is a proplist that gets passed to the template file. You can return other values in order to perform a redirect, set HTTP headers, or report an error. We’ll get to some of those, or you can read about controller return values in the Chicago Boss API docs. For now, we just want a single variable called “voters” which will contain the list we get from the database.
And… that’s it! Point your browser to see your very first app in action:
http://localhost:8001/voter/list
You should see your list of voters and their addresses. Cool, huh?
Whoever thought a telecom programming platform could be this easy? We have a fully functioning feature with only
- 2 lines of Model code
- 5 lines of View code
- 5 lines of Controller code
Now let’s have a little more fun. Let’s make a page for viewing our Notes on each voter. Start by modifying the list template, view/voter/list.html. Change Line 3 to have a link, like this:
<li><a href="/voter/view/{{ voter.id }}">{{ voter.first_name }} {{ voter.last_name }}</a>
Now open a new file called view/voter/view.html, and put this into it:
<h1>{{ voter.first_name }} {{ voter.last_name }}</h1> <b>Address</b><br /> {{ voter.address|linebreaksbr }}<br /> <b>Notes</b><br /> {{ voter.notes|linebreaksbr }}<br /> <a href="/voter/list">Back to the voter list</a>
Here we’re applying Django’s linebreaksbr filter to the Notes and Address, which just adds HTML breaks in the appropriate places.
All that’s left is the controller glue. Add this function to the end of voter_controller.erl:
view('GET', [VoterId]) -> Voter = boss_db:find(VoterId), {ok, [{voter, Voter}]}.
Remember how I said the second argument is a list of URL tokens? That means if the requestsd URL is /voter/view/voter-5, then the voter controller is called with the view action and [“voter-5”] is passed in as the token list. That makes our controller function quite simple, as you can see. Here we’re just using the boss_db:find/1 function, which we saw previously in the Chicago Boss shell (and which I warned you we’d be seeing a lot of!).
That’s all there is to our second feature. Point your browser to:
http://localhost:8001/voter/list
Click on a voter and voila! There are your notes! Two features in what, 20 minutes?
At this point you might be wondering how to deal with user input. User input usually needs to be owned by a user, so first we’re going to take a little detour and learn about associations, which lets one record belong to another. Don’t worry, the detour is short, won’t take but a few minutes of your time. Then we’re going to write a little log-in system, which will get your feet wet with processing POST data and also acquaint you with Boss’s authentication conventions. After that we’ll make a submission form and learn how to validate it, and as a final sendoff we’ll build an RSS feed to show you how to muck with date formatting and HTTP headers… because, hey, why not?
A web app of any substance will have records that refer to each other: books belong to users, comments belong to blog posts, and so on. These relationships will be defined in our model files. Let’s dive in and see exactly how Boss does it.
For our application, we’re going to make voters belong to Ward Bosses. Open up model/voter.erl and add an attribute called WardBossId which will tell us which Ward Boss a voter belongs to. The file should end up looking like this:
-module(Id, FirstName, LastName, Address, Notes, WardBossId]).
-compile(export_all).
Save but don’t close. Just for kicks, go open the voter /doc page,
http://localhost:8001/doc/voter
It should look the same as before, but now there’s an WardBossId attribute as well as ward_boss_id getter and setter functions.
Now let’s add some magic. Add the following line to model/voter.erl:
-belongs_to(ward_boss).
Save and close, then refresh the /doc page. Notice something new?
ward_boss() -> WardBoss
“Retrieves the ward_boss with Id equal to the WardBossId of this voter”
This -belongs_to line creates a function that gives us direct access to the voter’s WardBoss. Under the hood it just calls boss_db:find/1 on the voter’s WardBossId. But this generated function will be especially useful in templates, where we’ll be able to write code like:
{{ voter.ward_boss.name }}
Of course, we haven’t even defined a model file for Ward Bosses yet! Let’s go ahead and make it. Open a new file called model/ward_boss.erl, and put this in it:
-module(ward_boss, [Id, Name]). -compile(export_all). -has({voters, many}).
Now we’ve made an Ward Boss’s voters available in a function called voters(), and in our templates we’ll be able to write code like
{% for voter in ward_boss.voters %} ... {% endfor %}
Associations make MVC programming much, much easier. Of course, if you’ve seen this stuff before, you’ve probably been trained to worry about how many database calls we’re making under the hood, whether we’re prefetching the data, how to minimize database requests, etc. If that describes your thought processes, then stop it! I can’t say this enough: with Chicago Boss, database requests aren’t quite free, but they’re damn cheap. There’s no sense in counting queries at this point.
Anyway, before we start using these nifty belongs_to and has associations, let’s go ahead and let our Ward Bosses log in to the system, shall we?
We’re about to build a real log-in and session system, not just a kid’s little plaything. I’m going to go a little bit faster and not comment on every single line of code we paste in, so boys and girls, hold on to your hats.
The first thing we want to do is add an attribute for each Ward Boss’s password. Open up model/ward_boss.erl and add PasswordHash to the list of attributes. (Since there aren’t that many aldermen, we’ll let them use their real names to log in.) The first line should now look like this:
-module(ward_boss, [Id, Name, PasswordHash]).
Next we’re going to add some logic for recognizing that a Ward Boss is logged into a session, and a function for checking passwords. Add these lines to the model/ward_boss.erl file:
-define(SECRET_STRING, "Not telling secrets!"). session_identifier() -> mochihex:to_hex(erlang:md5(?SECRET_STRING ++ Id)). check_password(Password) -> Salt = mochihex:to_hex(erlang:md5(Name)), user_lib:hash_password(Password, Salt) =:= PasswordHash. login_cookies() -> [ mochiweb_cookies:cookie("user_id", Id, [{path, "/"}]), mochiweb_cookies:cookie("session_id", session_identifier(), [{path, "/"}]) ].
Here’s the strategy: when a log-in is successful, we’ll put that session identifier along with the Id into a cookie, and if we ever receive that same cookie back, we’ll know it’s the same person who logged in. No one will be able to forge sessions without knowing the secret string. So don’t tell anyone what it is!
Chicago Boss ships with mochiweb and its associated libraries. The function mochihex:to_hex/1 just turns an integer into a hex string, and mochiweb_cookies:cookie/3 generates an HTTP Set-Cookie header. If you want, you can check out the other utility functions in the src/mochiweb directory. We’ll be seeing these functions again before it’s all over.
Next let’s write a little log-in page. Create a directory in view called user, and open a new file called view/user/login.html. Put this into it:
{% if error %} <font color="red">{{ error }}</font> {% endif %} <form method="post"> Name:<br /> <input name="name" /><br /><br /> Password:<br /> <input name="password" type="password" /><br /><br /> <input type="submit" value="Log in" /> <input type="hidden" name="redirect" value="{{ redirect|default_if_none:"/voter/list" }}" /> </form>
(If you have a flair for design, I’m sure you can do better.) Note that we have a hidden field here for redirecting the user; this is useful in cases where we want to redirect the user depending on how they arrived at the log-in page. Save and close.
Now it’s time for the glue code: the controller. For starters, let’s make a function for the login action. We’ll make use of Erlang’s pattern-matching to distinguish the case when the user is loading the log-in page and when they’re sending us log-in data. To get started, open a new file called controller/user_controller.erl and add this at the top:
-module(user_controller, [Req]). -compile(export_all).
(Nothing different from what we saw in voter_controller.erl.) Now add the following to controller/user_controller.erl:
login('GET', []) -> {ok, [{redirect, Req:header(referer)}]};
Here we’re supplying the form with the URL of the page that the user came from, which is stored in the “Referer” HTTP header. We get access to the headers through Req, which as mentioned is a SimpleBridge request object with all sorts of goodies. In just a second we’ll use it to access POST parameters.
Now for the “hard” part, processing login credentials. If the password checks out, we’ll set the login cookies and redirect the user; otherwise, we’ll return some kind of error. Add this:
login(‘POST’, []) ->
Name = Req:post_param(“name”),
case boss_db:find(ward_boss, [{name, Name}], 1) of
[WardBoss] ->
case WardBoss:check_password(Req:post_param(“password”)) of
true ->
{redirect, proplists:get_value(“redirect”,
Req:post_params(), “/”), WardBoss:login_cookies()};
false ->
{ok, [{error, “Bad name/password combination”}]}
end;
[] ->
{ok, [{error, "No Ward Boss named " ++ Name}]}
end.
Save and close. We’re not quite done here. Back in our model file we referred to a function called user_lib:hash_password/1, which we haven’t actually written yet. Create a file called lib/user_lib.erl and put this into it:
-module(user_lib).
-compile(export_all).hash_password(Password, Salt) ->
mochihex:to_hex(erlang:md5(Salt ++ Password)).hash_for(Name, Password) ->
Salt = mochihex:to_hex(erlang:md5(Name)),
hash_password(Password, Salt).
Save and close. We are almost ready to start logging in users. Let’s go back to our voter list page and add a log-in link. Add this line to the top of view/voter/list.html:
<p><a href="/user/login">Log in</a></p>
Save and close. Now we just need a user! Let’s cheat and add it through the /admin interface. Navigate to:
http://localhost:8001/admin/model/ward_boss
Click “New ward_boss”. All we need to do is enter a name and a… password hash? Where do we get that?
Not to worry—go back to the Chicago Boss shell, the one with all the PROGRESS REPORTs that we did all the work in before. Type:
user_lib:hash_for(“Bathhouse John”, “password”).
That command will give you a password hash for the name “Bathhouse John” and the password “password”. Paste that hash (without the quotes, dummy) into the /admin form along with the name “Bathhouse John”, and then click “Create ward_boss”
Now we should have a user we can log in with. Let’s test it out. Go back to the voter list page:
http://localhost:8001/voter/list
Click the “Log in” link we created just a minute ago. Now attempt to log in as Bathhouse John. If all is well, you should be redirected to the voter list page; otherwise, you should get an error in red at the top.
Ok, now what? Well, the problem is that we’re not really using the login information. The next step is to protect our precious voter list, and maybe add a welcome message for the Ward Boss. We don’t want nobody nobody sent, now do we?
First things first: protect the voter list! Open up controller/voter_controller.erl and add this as the first function:
before_(_) -> user_lib:require_login(Req).
Before executing an action, Chicago Boss checks to see if the controller has an before_ function. If so, it passes the action name to the before_ function and checks the return value. If Boss gets a return value of {ok, Credentials}, it proceeds to execute the action, and it passes Credentials as the third argument to the action. If Boss instead gets {redirect, Location}, it redirects the user without executing the action at all. Note that if an action only takes two arguments, the before_ step is skipped altogether.
So to ensure before_ is invoked, we need to modify the actions to take a third argument. Modify the controller’s function definitions like so:
list(‘GET’, [], WardBoss) ->
view(‘GET’, [VoterId], WardBoss) ->
When we’re finished, these functions will have access to the BossRecord of the currently logged-in user (WardBoss). But for now, just save and close, because we need to define the require_login/1 logic.
Open up lib/user_lib.erl and add the following:
require_login(Req) -> case Req:cookie("user_id") of undefined -> {redirect, "/user/login"}; Id -> case boss_db:find(Id) of undefined -> {redirect, "/user/login"}; WardBoss -> case WardBoss:session_identifier() =:= Req:cookie("session_id") of false -> {redirect, "/user/login"}; true -> {ok, WardBoss} end end end.
That’s all there is to it. Save and close. Not only is the voter list now password-protected, but we can replace the log-in link with a welcome message. Open up view/voter/list.html and change the first line to:
<p>How The Kids, {{ ward_boss.name }}?</p>
All that’s left is the glue code. Open up controller/voter_controller.erl and change the list function to:
list('GET', [], WardBoss) -> Voters = boss_db:find(voter, []), {ok, [{voters, Voters}, {ward_boss, WardBoss}]}.
Save and close, and mosey on over to:
http://localhost:8001/voter/list
If everything worked, the page should now inquire after the well-being of your progeny. We can use the log-in information to do more than show a welcome message, of course; before it’s over we’ll be showing each Ward Boss only the voters in their own ward. But first we want them able to submit voter information for themselves. To do that, we need to know how to validate input.
We’ve already seen a simple example of validating input when we checked the username and password against the database. But we what if we want to check a form and report all of its errors at once? As it turns out, Erlang’s language features make it extremely easy to define a list of tests and collect all the tests that fail. We’ll go through a simple example as we build a voter registration form.
To start out, let’s write the form, including a little logic for displaying errors. Open a new file called view/voter/register.html and put into it:
{% if errors %} <ol> {% for error in errors %} <li><font color=red>{{ error }}</font> {% endfor %} </ol> {% endif %} <form method="post"> First and last name:<br /> <input name="first_name" value="{{ voter.first_name|default_if_none:"" }}" /> <input name="last_name" value="{{ voter.last_name|default_if_none:"" }}" /><br /><br /> Address<br /> <textarea name="address">{{ voter.address|default_if_none:"" }}</textarea><br /><br /> Notes<br /> <textarea name="notes">{{ voter.notes|default_if_none:"" }}</textarea><br /><br /> <input type="submit" value="Submit form" /> </form>
Next let’s add some validation code to the model. As of v0.3.3, Chicago Boss has some built-in validation machinery. To add tests, we need to add a function called voter:validation_tests/0 which returns a list of {TestMessage, TestFunction} tuples. The TestFunction will be a fun of arity 0. You’ll see in a second. Open up model/voter.erl and add the following:
validation_tests() -> [{fun() -> length(FirstName) > 0 end, "Please enter a first name"}, {fun() -> length(LastName) > 0 end, "Please enter a last name"}, {fun() -> string:str(Address, "Chicago") > 0 end, "Address is not in Chicago! What good is that??"}].
That’s all we need to do in the model. If any of these tests fail, we’ll get a list of errors when trying to save the record.
Finally, we modify the controller code, controller/voter_controller.erl. Add this:
register('GET', [], WardBoss) -> {ok, []}; register('POST', [], WardBoss) -> Voter = voter:new(id, Req:post_param("first_name"), Req:post_param("last_name"), Req:post_param("address"), Req:post_param("notes"), WardBoss:id()), case Voter:save() of {ok, SavedVoter} -> {redirect, "/voter/view/"++SavedVoter:id()}; {error, Errors} -> {ok, [{errors, Errors}, {voter, Voter}]} end.
And we’re done. It might seem weird that we return ok when there are errors, but we’re just telling Chicago Boss that we’ve got everything under control.
Try out the new feature by pointing your browser to:
http://localhost:8001/voter/register
For kicks, type in a voter who’s not in Chicago and see what happens. Then try one with an address in Chicago. If everything works, it should take you to our beautiful voter information page. If so, congratulations! You are now processing and validating user input!
Now we’re in a position to really take advantage of the log-in information. Let’s change the voter list to show only voters registered by the person who is logged in. Surprisingly, we’ll end up removing more code than we add!
First open up view/voter/list.html, and change this line:
{% for voter in voters %}
To:
{% for voter in ward_boss.voters %}
Also, while we’re there, add a link to the voter registration page to the bottom:
<p><a href="/voter/register">Register a new voter</a></p>
Save and close. Next open up controller/voter_controller.erl. Rip out the current implementation of list and replace it with:
list('GET', [], WardBoss) -> {ok, [{ward_boss, WardBoss}]}.
Rather than search the database ourselves, we take advantage of the has association and do all the querying that way. Point your browser back to:
http://localhost:8001/voter/list
You should just see the voters that you created through your form, and none of the voters we made with the /admin interface. Go ahead and create a couple more voters using your applicatin. You made it, now play with it!
One feature we’re still missing is the feature a real Ward Boss from Chicago will want most: the ability to cover his tracks!
First let’s provide a log-out link next to our welcome message. Change the first line of view/voter/list.html to this:
<p>How The Kids, {{ ward_boss.name }}? (<a href="/user/logout">Quick, Log Me Out!</a>)</p>
The log-out link will just reset the user’s cookies and redirect somewhere, so we don’t need a new template file for it. Let’s pop open controller/user_controller.erl and add a new function:
logout('GET', []) -> {redirect, "/", [ mochiweb_cookies:cookie("user_id", "", [{path, "/"}]), mochiweb_cookies:cookie("session_id", "", [{path, "/"}]) ]}.
That will redirect to the homepage and blank out the credential cookies. Go ahead and try it out. Refresh the voter list page (/voter/list
) and click “Quick, Log Me Out!”
Ah, back to the home page. I guess that nearly concludes our guided tour. At this point we have a full-fledged log-in app. Now all we need is some kind of RSS feed…
You want to be notified about all those new voters, right?
We’re going to start by adding a CreationTime attribute to the voter model so we can put that into the feed. Open up model/voter.erl and change the first line to:
-module(voter, [Id, FirstName, LastName, Address, Notes, WardBossId, CreationTime]).
We don’t worry too much about migrating the data because Chicago Boss will automatically fill in blanks on the old data next time a particular record retrieved (and drop obsolete fields next time a particular record is updated). We might end up with some weird Year 0 values in existing data, but we can deal.
Anyway, attributes that end in “Time” are treated specially; instead of passing a String to new, we need to pass in either a DateTime tuple or an erlang:now() object. Let’s modify the call to voter:new in order to use erlang:now() as the last argument. Open up controller/voter_controller.erl and change these lines:
Voter = voter:new(id, Req:post_param(“first_name”), Req:post_param(“last_name”),
Req:post_param(“address”), Req:post_param(“notes”), WardBoss:id()),
To:
Voter = voter:new(id, Req:post_param("first_name"), Req:post_param("last_name"), Req:post_param("address"), Req:post_param("notes"), WardBoss:id(), erlang:now()),
Now we can get busy with the RSS feed. Open up a new template called view/voter/rss.html (don’t worry about the .html extension, we’ll get the MIME type right in a minute). You might be accustomed to fancy RSS generation tools, but forget it, it’s easy enough to get things done the old-fashioned way so here we go. Just paste this in:
{% autoescape on %}<?xml version="1.0" encoding="utf-8"?> <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/"> <channel> <title>Chicago Votes</title> <atom:link href="http://localhost:8001/voter/rss" rel="self" type="application/rss+xml" /> <link>http://localhost:8001/voter/list</link> <description>All the folks that's fit to vote!</description> <language>en-us</language> {% for voter in voters %} {% if forloop.first %} <lastBuildDate>{{ voter.creation_time|date:"r" }}</lastBuildDate> {% endif %} <item> {% if voter.ward_boss %} <dc:creator>{{ voter.ward_boss.name }}</dc:creator> {% endif %} <guid isPermaLink="true">http://localhost:8001/voter/view/{{ voter.id }}</guid> <link>http://localhost:8001/voter/view/{{ voter.id }}</link> <title>{{ voter.first_name }} {{ voter.last_name }}</title> <description>{{ voter.notes }}</description> <pubDate>{{ voter.creation_time|date:"r" }}</pubDate> </item> {% endfor %} </channel> </rss>{% endautoescape %}
Notice the date filter applied to voter.creation_time. This filter makes our lives a hundred times easier in terms of generating an RFC-822 timestamp, so I kind of lied when I said we didn’t have fancy RSS generation tools.
Now we just need to get it glued up. Open up controller/voter_controller.erl and add an action. For now we won’t worry about authentication since most RSS feeds are pretty much a secret anyhow.
rss('GET', []) -> Voters = boss_db:find(voter, [], 20, 0, creation_time, num_descending), {ok, [{voters, Voters}]}.
Internally, dates are stored as integers, so we want to sort numerically (i.e. num_descending). This is the most complicated call to boss_db:find that we’ve yet seen, but you can find the full documentation for it over in the Chicago Boss API docs.
Point your browser over to the new RSS feed to see what it looks like:
http://localhost:8001/voter/rss
Neat, huh? Now I’m sure you’re thinking, WAIT, WHAT ABOUT THE MIME TYPE?? I almost forgot. Change the last line of that rss function to:
{ok, [{voters, Voters}], [{"Content-Type", "application/rss+xml"}]}.
We send Boss additional HTTP headers to return to the client in the third tuple element of the return value, so that’s all we need to do to get the MIME type right.
Done!
You’re on your own now, kid. To see the (growing) list of other things you can do with Boss, check out the Chicago Boss API docs, and if you want to connect with other Boss users, be sure to join the Chicago Boss mailing list.
“Says who?” “Says Boss.”
This guide has covered most of the state of the art of Chicago Boss. It’s a simple framework that packs a powerful punch. Although there’s still a lot of work to be done, I think the design of Boss will make future improvements quite easy.
One class of improvements relates to deployment, code upgrades, and monitoring. I wrote Boss to work on a tiny VPS rather than a huge server cluster, so it’s not a big issue for me personally. But given that Chicago Boss is built with Erlang, it should be possible to upgrade code on the fly with no down-time if somebody writes the right scripts. However, it’s not there yet.
As you probably noticed during this tour, the /admin interface could use a lot of love. I wrote it in a couple hours, and besides needing aesthetic improvement, it needs to be able to edit records and deal with Time and ForeignId fields more intelligently. And sort the search results.
Fourth, Boss needs a module for doing A/B testing. What I’d really like to see with Boss is a module that implements Bayesian experimental design, which avoids some of the problems associated with traditional A/B tests. It’d be even better if Chicago Boss decided for you whether A or B was better and then emailed you a patch getting rid of the losing code.
Fifth, I’d like to see some kind of integration with Nitrogen. Nitrogen’s magic is mostly on the front-end, whereas Boss really shines on the backend. Both Boss and Nitrogen are housed in Rusty Klophaus’s excellent SimpleBridge, so they ought to fit together quite well. I’ve even thought of an irresistible name for the project: BOSS IN SPACE!!
If you might want to get involved in the development of Chicago Boss, or keep up with Chicago Boss news and updates, or just have a couple Chicago Boss questions, the best thing to do is join the new Chicago Boss mailing list.
Chicago Boss is compatible with one and only one database server: Tokyo Tyrant. Unfortunately, Tokyo Tyrant isn’t yet available with a simple “apt-get,” so this section will go through the details of setting up a Tyrant server on a Mac or Linux machine. It might be the hardest part of the whole guide.
I am told that Mac users can install both Cabinet and Tyrant via MacPorts.
Tokyo Tyrant requires a software library called Tokyo Cabinet.
Mac/Linux:
Now we’re ready for the Tyrant.
Mac and Linux:
tar xzf tokyotyrant-1.1.40.tar.gz
cd tokyotyrant-1.1.40
./configure
make
sudo make install
If everything went smoothly, Tokyo Tyrant is now installed on your system. To get it up and running in your home directory, just do
ttserver -log ttserver.log boss-test.tct
That will create a table database called boss-test.tct right in your home directory, along with a log file called ttserver.log. You can stop the database at any time with Control-C. This setup will work fine for now, but if you’re planning to do serious development, you will want Tokyo Tyrant running as a daemon under its own username with files in its own directory. The following section will explain how to do that.
Open up boss.config and change this line:
To this:
You can also set additional database options db_host and db_port if you have a non-standard configuration.
First, create a user for Tokyo Tyrant:
sudo adduser —system —no-create-home —group ttserver
Next create the three directories that Tyrant will write files in:
sudo mkdir /var/lib/ttserver
sudo mkdir /var/log/ttserver
sudo chown ttserver:ttserver /var/*/ttserver
Finally, Tyrant will need its own init.d script so that it automatically starts up when the computer boots. Here’s the script I use, just save it to /etc/init.d/ttserver:
Download my /etc/init.d/ttserver
Try out the script by starting up Tyrant:
sudo /etc/init.d/ttserver start
If everything seemed to work, we need to tell the system that we want this script to be run at startup and shutdown. Do it with:
sudo update-rc.d ttserver defaults
Congratulations. You now own your very own Tyrant.
- February 28, 2011: Update to Chicago Boss 0.4.5; fix bug in SavedGreeting command; add note about enabling SSL via MacPorts
- September 7, 2010: Update to Chicago Boss 0.4.0 (Tyrant is optional; Web/ is abolished)
- June 21, 2010: Bugfix: “has_many” is supposed to be “has”
- June 15, 2010: Update to Chicago Boss 0.3.6 (views and controllers are now in the Web/ directory)
- May 27, 2010: Update save() semantics; use new model validation mechanism; update to ChicagoBoss 0.3.3.
- May 22, 2010: Update to ChicagoBoss 0.3.2; rewrite introduction
- May 20, 2010: Break “Chicago Boss’s Big Secret” into a separate article
- April 30, 2010: Install Tokyo Cabinet from sources on Linux (apt-get provides wrong version); update wget command to ChicagoBoss-0.3.1 to prevent problems issuing DB commands before loading the “Hello, World!” page
- April 29, 2010: Initial draft