Skip to content

An Evening With Chicago Boss

Sebastian edited this page May 20, 2015 · 27 revisions

An Evening With Chicago Boss

By Evan Miller

Part I. Introduction

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, emmiller@gmail.com.

Let’s get started!

Part II. Building With Boss

“You leave the thinkin’ to me.” —Boss

II.0. Table of Contents

Here’s the roadmap, pilgrim:

  1. Learning Erlang
  2. Installing Erlang
  3. My First Boss
  4. Essential Tricks: /admin and /doc
  5. Model, View and Controller
    1. Model
    2. View
    3. Controller
    4. All Together Now
  6. Associations: belongs_to and has
  7. How To Implement Log-ins
  8. How To Actually Use Log-In Information
  9. How To Validate Input
  10. How To Implement A Log-Out
  11. How To Implement an RSS feed


II.1. Learning Erlang

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.


II.2. Installing Erlang

Chapter 2 of the Armstrong book explains how to download and install Erlang. Here’s the gist of it:

Linux (Debian/Ubuntu):

sudo apt-get install erlang

or (RedHat, CentOS, Fedora):

su -c 'yum install erlang'

Mac:

Using Homebrew:

brew install erlang

Using MacPorts (with the +ssl option),:

port install erlang +ssl

From source:

There are several configurations to install from source on mac. It’s probably best to read through the instructions available on the Basho page: Installing Erlang especially for those on 10.7

Windows:

Chicago Boss works on Windows when you follow Windows instructions.

II.3. Database options

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. For a list of all available databases, visit GitHub repository of Boss DB adapters.


II.4. My First Boss

We’re finally ready to have some fun. First, download Chicago Boss, untar, and cd:

wget https://github.com/ChicagoBoss/ChicagoBoss/archive/v0.8.15.tar.gz
tar xzf v0.8.15.tar.gz
cd ChicagoBoss-0.8.15

The way things are in Chicago Boss 0.8.15 is that you need to build Chicago Boss first and then create a new project that will host your code.

To build Chicago Boss and create a new project:

make
make app PROJECT=evening

Now change directory into the new projects and start it (there will be a lot of PROGRESS REPORTS flying by on your terminal but hopefully there is no big scary error messages):

cd ../evening
./init-dev.sh

Open http://localhost:8001/ in your browser and you should see a message: No routes matched the requested URL

(Obviously, if you’re running Boss on another machine, change localhost to the name of that machine.)

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 create a hello world message which we later will update to show a different greeting from a database. Put this into src/controller/evening_greeting_controller.erl

-module(evening_greeting_controller, [Req]).
-compile(export_all).

index('GET', []) ->
    {output, "<strong>Hello World!</strong>"}.

After you save the controller, run ./rebar compile and open http://localhost:8001/greeting/index

Create model for greetings in src/model/greeting.erl

-module(greeting, [Id, GreetingText]).
-compile(export_all).

Open http://localhost:8001/doc/greeting to see list of functions that the framework provides for all your models of the box.

Right now, you do not have greetings created and if you type in the shell with all the PROGRESS REPORTs a command to fetch all greetings and assign them to a variable, your result will be an empty Erlang list.

1> InitialGreetings = 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:

[]

because there are no greetings in the database. Let’s create one greeting and save it to the database.

2> FirstGreeting = greeting:new(id, "Boss says Hello!").
{greeting,id,"Boss says Hello!"}
3> FirstGreeting:save().
{ok, {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 of our saved greetings:

4> Saved = boss_db:find(greeting, []).
[{greeting,"greeting-1","Hello"}].
5> Greeting = hd(Saved). % Pop the only BossRecord off the list
{greeting,"greeting-1","Boss says Hello!"}

Now let’s invoke some powers:

6> Greeting:attribute_names().
[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:

7> Greeting:greeting_text().
"Boss says Hello!"
8> 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:

9> NewGreeting = Greeting:set(greeting_text, "Do I look like Santa Claus?").
{greeting,"greeting-1","Do I look like Santa Claus?"}

Now let’s update the controller to show the greeting that we just saved using the same commands that we typed in the console.

-module(evening_greeting_controller, [Req]).
-compile(export_all).

index('GET', []) ->
    SavedGreetings = boss_db:find(greeting, []),
    FirstGreeting = hd(SavedGreetings),
    {output, FirstGreeting:greeting_text()}.

Now go back to your web browser and open URL to execute the controller. Do you see the new message “Do I look like Santa Clause?” 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:

10> NewGreeting:save().
{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:

11> MyGreeting = greeting:new(id, "Where's mine?").
{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:

12> MyGreeting:id().
id

And the greeting_text:

13> MyGreeting: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:

14> {ok, SavedGreeting} = MyGreeting:save().
{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:

15> SavedGreeting:id().
"greeting-2"

Since the new greeting has an ID, we know for sure it’s in the database. Let’s prove it:

16> boss_db:find(greeting, []).
[{greeting,"greeting-1","Do I look like Santa Claus?"},
 {greeting,"greeting-2","Boss says Hello!"},
 {greeting,"greeting-3","Where's mine?"}]

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:

17> boss_db:find("greeting-1").
{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:

18> boss_db:type("greeting-1").
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 the first greeting that we created:

19> boss_db:delete("greeting-1").
ok

And to prove it’s not in the database any longer:

20> boss_db:find(greeting, []).
[{greeting,"greeting-2","Boss says Hello!"},
 {greeting,"greeting-3","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:

21> boss_db:count(greeting).
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.


II.5. Essential Tricks: /admin and /doc

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. It used to be included by default with every ChicagoBoss installation and now it comes as a separate application that can be installed along side ChicagoBoss.

Download ChicagoBoss Administration application from GitHub https://github.com/ChicagoBoss/cb_admin to reside in the same parent directory that ChicagoBoss and “evening” source code is:

|-ChicagoBoss
|-evening
|-cb_admin

git clone git://github.com/ChicagoBoss/cb_admin.git
cd cb_admin
./rebar compile

Update evening/boss.config for the “evening” application to let ChicagoBoss framework know that when you start the “evening” application, cb_admin application also needs to be started.

[{boss, [
    {path, "../ChicagoBoss"},
    {vm_cookie, "abc123"},
    {applications, [evening, cb_admin]},
    {db_host, "localhost"},
    {db_port, 1978},
    {db_adapter, mock},
    {log_dir, "log"},
    {server, mochiweb},
    {port, 8001},
    {session_adapter, mock},
    {session_key, "_boss_session"},
    {session_exp_time, 525600}
]},
{ evening, [
    {path, "../evening"},
    {base_url, "/"}
]},
{cb_admin, [
    {path, "../cb_admin"},
    {allow_ip_blocks, ["127.0.0.1"]},
    {base_url, "/admin"}
]}
].

If the evening application is running in an Erlang console, shut it down with Ctrl+D and start it back up using ./start-dev and it will use the information about cb_admin from the updated boss.config. Once the “evening” application starts, point your browser to http://localhost:8001/admin

Click on Models tab and then “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.


II.6. Model, View, and Controller

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.


II.6.1. Model

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.

Parameters that end with Id or Time have special meaning, which we’ll discuss later.

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.


II.6.2. View

Now let’s build a little interface of our own. Create a directory in src/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.


II.6.3. Controller

Now all we need is some glue code: the controller. Open up a new file called src/controller/evening_voter_controller.erl and put the following lines into it:

-module(evening_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(evening_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.


II.6.4. All Together Now

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 evening_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?

II.7. Associations: belongs_to and has

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(voter, [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?


II.8. How To Implement Log-ins

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/evening_user_controller.erl and add this at the top:

-module(evening_user_controller, [Req]).
-compile(export_all).

(Nothing different from what we saw in evening_voter_controller.erl.) Now add the following to controller/evening_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}], [{limit,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?


II.9. How To Actually Use Log-In Information

First things first: protect the voter list! Open up controller/evening_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:

From:

list('GET', []) ->
view('GET', [VoterId]) ->

To:

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/evening_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.


II.10. 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/evening_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/evening_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!


II.11. How To Implement A Log-Out

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/evening_user_controller.erl and add a new function:

logout('GET', []) ->
    {redirect, "/user/login",
        [ mochiweb_cookies:cookie("user_id", "", [{path, "/"}]),
            mochiweb_cookies:cookie("session_id", "", [{path, "/"}]) ]}.

That will redirect to the login page 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 login 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…


II.12. How To Implement an 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/evening_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/evening_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, [], [{limit,20},
    {offset,0},{order_by,creation_time},{descending,true}]),
    {ok, [{voters, Voters}], [{"Content-Type", "application/rss+xml"}]}.

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.

Part III. The Future Of Chicago Boss

“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.


Appendix A: Meet The Tyrant

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.


A.1. Download and Install Tokyo Cabinet

Tokyo Tyrant requires a software library called Tokyo Cabinet.

Mac/Linux:

    wget http://fallabs.com/tokyocabinet/tokyocabinet-1.4.47.tar.gz
    tar xzf tokyocabinet-1.4.47.tar.gz
    cd tokyocabinet-1.4.47
    ./configure
    make
    sudo make install


A.2. Download and Install Tokyo Tyrant

Now we’re ready for the Tyrant.

Mac and Linux:

    wget http://fallabs.com/tokyotyrant/tokyotyrant-1.1.41.tar.gz
    tar xzf tokyotyrant-1.1.41.tar.gz
    cd tokyotyrant-1.1.41
    ./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

   cd ~
   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.

A.3. Configure Boss To Use Tyrant

Open up boss.config and change this line:

    {db_driver, boss_db_driver_mock},

To this:

{db_driver, boss_db_driver_tyrant},

You can also set additional database options db_host and db_port if you have a non-standard configuration.


A.4. Configure Tyrant As A Standalone Daemon (Optional, Ubuntu only)

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/run/ttserver<br />
    sudo mkdir /var/lib/ttserver<br />
    sudo mkdir /var/log/ttserver<br />
    sudo chown ttserver:ttserver /var/*/ttserver<br />

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.


Appendix B: Changelog



  • March 8, 2012: Update to Chicago Boss 0.7.2

  • 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