Friday, October 2, 2009

First Steps With Chicago Boss

All right. Well, hopefully you found a delightful Chicago Boss graphic greeting you as you completed the Getting Started article. But that wasn't particularly satisfying. So let's build a simple "todo" application and examine the steps (and pitfalls and caveats) as we go.

1. The Models

Our simple applicaiton will have two model definitions, one for "categories" of todo items (e.g. "personal", "work") and one for the todo items themselves.

Model/todo_category.erl:
-module(todo_category, [Id, Name, Description]).
-compile(export_all).
-has_many(todo_items).

Model/todo_item.erl:
-module(todo_item, [Id, TodoCategoryId, Name, Description]).
-compile(export_all).
-belongs_to(todo_category).

OK. A word about "belongs_to" -- you must have a CamelCaseId in the main module definition which corresponds exactly to the corresponding model referenced by "belongs_to". It could have been placed at the end of the list, e.g. "-module(todo_item, [Id, Name, Description, TodoCategoryId])." but I tend to put these "foreign key" bits at the beginning. YMMV.

2. The Controller

There's a lot going on in this controller, I'll explain it later. First, the code:

Controller/todo_controller.erl:
-module(todo_controller).
-compile(export_all).

item_list(Req) ->
    Items = boss_db:find(todo_item, [], 100),
    {ok, [{item_list, Items}]}.

items_by_category(Req) ->
    CategoryId = Req:get_qs_value("category_id"),
    Category = boss_db:find(CategoryId),
    Items = boss_db:find(todo_item, [{todo_category_id, CategoryId}], 100),
    {ok, [{item_list, Items}, {category, Category}]}.

new_item_form(Req) ->
    Categories = boss_db:find(todo_category, [], 100),
    {ok, [{category_list,Categories}]}.

new_item_submit(Req) ->
    CategoryId = Req:get_post_value("item_category_id"),
    Name = Req:get_post_value("item_name"),
    Description = Req:get_post_value("item_description"),
    Item = todo_item:new(id, CategoryId, Name, Description),
    SavedItem = Item:save(),
    {redirect, lists:concat(["item?id=", [SavedItem:id()]])}.

item(Req) ->
    ItemId = Req:get_qs_value("id"),
    Item = boss_db:find(ItemId),
    {ok, [{item, Item}]}.

category_list(Req) ->
    Categories = boss_db:find(todo_category, [], 100),
    {ok, [{category_list,Categories}]}.

new_category_submit(Req) ->
    Name = Req:get_post_value("category_name"),
    Description = Req:get_post_value("category_description"),
    Category = todo_category:new(id, Name, Description),
    SavedCategory = Category:save(),
    {redirect, "category_list"}.

category(Req) ->
    CategoryId = Req:get_qs_value("id"),
    Category = boss_db:find(CategoryId),
    {ok, [{category, Category}]}.

All right. First a word about controllers in general. Chicago Boss automatically (convention, not configuration) routes requests to /some/word to Controller/some_controller.erl:word(Req) if that controller and method exist. For a simple html file, these don't have to exist. We'll see an example of that in a moment.

What I've done here is define a few web entry points to the application: "/todo/item_list", "/todo/items_by_category", "/todo/new_category_submit", etc. Most of these methods return a simple "ok" tuple along with some data. Some of them, the callbacks for create form submission, use "redirect" to send the user along to an appropriate page instead of simply presenting a "OK, thanks for your submission" page.

You also see a few of the methods available: Req:get_qs_value to get a value from a query string, and Req:get_post_value to get a value from a form submit. Of particular interest might be the "boss_db:find" examples. We could add these as ease-of-use functions in our models themselves, but for now, let's go with what we have. (Noting that I'm not showing update or delete paths yet in this snippet: so far just create and read.)

3. The Views

All right. We have defined a model and some controller routes for our web application, but to present data back to the user, we'll need some views. I'm not going to present all the views necessary to implement this application, or present them in full xhtml-strict compliance glory (what would be the fun in that?) but I will give a few of the nicer bits which show off some of the templating goodness available.

View/todo/item_list.html:
<table>
  <tr>
    <th>category</th>
    <th>name</th>
    <th>description</th>
  </tr>

{% for item in item_list %}
  <tr>
    <td><a href="category?id={{ item.todo_category.id }}">{{ item.todo_category.name }}</a></td>
    <td><a href="item?id={{ item.id }}">{{ item.name }}</a></td>
    <td>{{ item.description }}</td>
  </tr>
{% endfor %}
</table>

<a href="new_item_form">create new</a>

All right. A for loop and some inline string data. Whee. Well, actually, that's pretty much all you need to do 90% of what a view should be doing. There's more under the covers, but that will have to wait until a more in-depth article on the view template features.

4 comments:

Anonymouz said...

Looking forward to the next post.

grantmichaels said...

I'm tuned in as well ...

Caz vonKow said...

Seems like the first line of todo_controller.erl should read "-module(todo_controller, [Req]).", other than that, sweet tutorial, I'm looking forward to more. I'm starting to think the Boss might be able to replace Django for me...

karmen said...

also looking forward to next post. :)