Alson Kemp

Turbinado: Implementing a poor-man’s wiki

*Updated* to reflect modifications to the ORM (No “Model” suffix; fractured in Types, Functions, Relations)

These are very early days for Turbinado, so much change is going on… But here’s a quick tutorial on putting together a poor man’s page editor/manager in Turbinado.

### Build Turbinado
*Warning!*: With 6.10’s changes in dynamic plugins, Turbinado only builds with GHC 6.8 right now. Fixing this is next up in the dev queue.

You’ll need to have the following packages installed to have a go at installation:

GHC 6.8 *(darcs)*
haskell-src-exts *(darcs)*
harp *(darcs)*
hslogger *(git)*
encoding *(darcs)*
hsx *(darcs)*
hs-plugins *(darcs)*
http *(darcs)*
HDBC *(git)*
HDBC-PostgreSQL *(git)*

**Grab the code**

git clone git://github.com/alsonkemp/turbinado.git

**Build it**

With all of the packages installed, wait for a new moon, stand on tip-toes, and do the following:

runghc Setup.lhs configure
runghc Setup.lhs build

### Configure It

cp Config/App.hs.sample     Config/App.hs
cp Config/Routes.hs.sample Config/Routes.hs

Edit App.hs and Routes.hs to taste.

### Create the Page table

    title character varying NOT NULL,
    content text NOT NULL,
    _id character varying(255) NOT NULL PRIMARY KEY,

Updated: added PRIMARY KEY which is needed by the ORM generator.
### Generate the ORM models

runghc scripts/GenerateModels

You should now have an interesting file or two in App/Models.  The files are organized as follows:
* App/Models/Bases/Comment.hs – all base Models inherit this.
* App/Models/Bases/AbcXyzType.hs – the representation of the database table *abc_xyz*. Don’t edit this! It’s autogenerated and your changes will get ignored on the next gen.
* App/Models/Bases/AbcXyzFunctions.hs – CRUD functions on *abc_xyz*. Don’t edit this! It’s autogenerated and your changes will get ignored on the next gen.
* App/Models/Bases/AbcXyzRelations.hs – functions on tables related to *abc_xyz*. Don’t edit this! It’s autogenerated and your changes will get ignored on the next gen.
* App/Models/AbcXyz.hs – the user configurable area of the AbcXyz. By default this just imports the above three tables. All of your custom *find*, *insert* and *update* methods go here.

A big shout out to .netTiers for pointing the way on building a code-generator ORM.

### Create your Layout
Just as in Rails, the Layout usually defines the majority of your sites page structure. See here for the Layout used for turbinado.org.

### Create your Page controller
The controller handles the request and sets up ViewData for the View to render/display. This little Page controller has the following functions/methods/actions:

  • index: list all Pages.
  • show: render one Page.
  • new: display a blank Page form.
  • create: take the submission of the ‘new’ action.
  • edit: display an existing Page in a form for editing.
  • save: take the submission of the ‘edit’ action.

Full version here. Here’s a snippet:

-- 'id' is an important function name in Haskell, so I use id' for the Page's "id".  There's got
-- to be a better solution

-- This is the generated ORM for the 'page' table in the database.
import qualified App.Models.Page

-- Index lists out all of the pages
-- setViewDataValue is used to store data for retrieval by the View.  Idea lifted from ASP.NET 
-- http://msdn.microsoft.com/en-us/magazine/cc337884.aspx
index :: Controller ()
index  = do pages <- App.Models.Page.findAll --use ORM goodness to get all pages
            setViewDataValue "pages-list" $ map (\p -> (title p, _id p)) pages

-- Show shows one page
-- Notes:
--  * The "id" is parsed from the request's path and put into the Settings.
--     See: http://github.com/alsonkemp/turbinado-website/tree/master/Config/Routes.hs
--  * getSetting returns a "Maybe a".  getSetting_u is the unsafe version and returns just the "a".
show :: Controller ()
show  = do id'  <- getSetting_u "id"
           p <- App.Models.Page.find id'
           setViewDataValue "page-title" (title p)
           setViewDataValue "page-content" (content p)

-- snip --

save :: Controller ()
save = do   id'  <- getSetting_u "id"
            _title   <- getParam_u "title"
            _content <- getParam_u "content"
            p <- App.Models.Page.find id'
            App.Models.Page.update p {title = _title, content = _content}
            redirectTo $ "/Page/Show/" ++ id'

### Create Your Views
This Controller requires 4 views:

* Index
* Show
* New
* Edit – this could probably be the same as New.

See here for pre-built views. Here’s the “Index” view (note: *this isn’t very sugary and that makes me **sad** *):

page =  <div>
            Page Index
          <% do ls <- getViewDataValue_u "pages-list" :: View [(String, String)]
                mapM indexItem ls

-- fugly, but I'm still getting used to HSP-like templating
indexItem (t,i) = return $ cdata $ unlines $
                   ["<div style='padding: 0pt 5px;'>"
                   ," <a href=\"/Page/Show/" ++ i ++"\">"
                   ,"  "++ t
                   ," </a>"

Here’s the “Show” view and it’s *pretty sugary*:

page = <div>
          <h1><% getViewDataValue_u "page-title" :: View String %></h1>
          <% getViewDataValue_u "page-content" :: View String %>

### Run It
Start up Turbinado:

dist/build/turbinado/turbinado -p 1111

Browse to it: http://localhost:1111/Page/Index (it’ll take a couple of seconds to compile the Model, Controller and View)

### Examples!
There’s still lots of work to do on the ORM and on Documentation. As the code base matures, both should progress. Examples are a nice way to drive development forward and to engage the community, so let me know if you’d like to see anything in particular demonstrated and I’ll try to implement it.

Written by alson

December 20th, 2008 at 8:08 pm

Posted in Haskell,Turbinado

with 18 comments

18 Responses to 'Turbinado: Implementing a poor-man’s wiki'

Subscribe to comments with RSS or TrackBack to 'Turbinado: Implementing a poor-man’s wiki'.

  1. I have one request, which you can freely ignore if you think it’s too early for this.

    I haven’t tried this out yet, but from your list of dependencies, it seems that turbinado depends on development versions of various libraries (your ‘darcs’ and ‘git’ annotations mean that, right?)

    This makes it somewhat more difficult for a casual programmer to try things out; therefore, my suggestion is that you should try to make the deps release/hackage versions.

    Vesa Kaihlavirta

    22 Dec 08 at 4:36 am

  2. Vesa,
    Excellent point. Right now, building Turbinado is a huge challenge. I’ve got to get a couple of architectural bits fixed up and get this thing ported over to 6.10. Then I’ll focus on getting the build simplified. I really wish that “cabal install” was working well because that would greatly ease building Turbinado.

    • Alson


    22 Dec 08 at 2:50 pm

  3. Hi,I tried to do follow the example, but I couldn’t even install the packages, hsx and hs-plugins were impossible. I tried with cabal and manually, and neither of those worked . any suggestion, known issue with this packages?

    Adolfo Builes

    26 Dec 08 at 2:32 pm

  4. Adolfo,

    Sounds as if I should focus on getting this thing to be easily buildable, eh? Many of the packages used by Turbinado are in flux right now, so it’s a pretty difficult thing to build. I’ll get that fixed up this coming week.

    Sorry for the hassle, but thank you for the feedback!



    26 Dec 08 at 2:35 pm

  5. […] here, Adolfo commented: “Hi,I tried to do follow the example, but I couldn’t even install the […]

  6. +1 on impossible to build

    I’m very excited about the project though and would love to find a way to contribute but I can’t get past hs-plugins! Any advice on how to get this built? I’ve tried on XUbuntu and Mac OS 10.5 with no luck on either…

    Jason Dew

    3 Jan 09 at 7:49 pm

  7. Jason,

    Damn! I’m working right now on making the build easier, but I’m not sure that that will fix your plugins issues. hs-plugins is probably deprecated in 6.10 (since 6.10 exposes the ghc-api), so Turbinado will migrate to using ghc-api then.

    You weren’t able to build hs-plugins with XUbuntu? That’s really weird. I can build fine in Debian. What error in plugins are you getting?



    3 Jan 09 at 7:55 pm

  8. I uninstalled everything haskell-related and reinstalled (making sure I’m using GHC 6.8.*) and I had no problems with hs-plugins this time. I’m getting harp and hsx from darcs and building those now… will let you know if I have success. Thanks!

    Jason Dew

    3 Jan 09 at 8:00 pm

  9. This pretty much sums up where I’m at (dependency hell)…

    phoenix hsx # runhaskell Setup.hs configure
    Configuring hsx-0.4.6…
    Setup.hs: At least the following dependencies are missing:
    haskell-src-exts ==0.4.* && ==0.4.*, utf8-string -any && -any
    phoenix hsx # cd ../haskell-src-exts/
    phoenix haskell-src-exts # runhaskell Setup.hs configure
    Configuring haskell-src-exts-0.4.6…
    Setup.hs: At least the following dependencies are missing:
    base >=4, cpphs >=1.3
    phoenix haskell-src-exts # cabal install cpphs
    Resolving dependencies…
    cabal: There is no installed version of ghc-prim

    Jason Dew

    3 Jan 09 at 9:16 pm

  10. Jason,

    Yeah. I’m going to update Turbinado to 0.4 tonight and include all required dependencies. You’ve got some problems there because that version of haskell-src-exts require GHC 6.10, but you’re using 6.8…

    Check the repo tomorrow and all deps will be included in turbinado/tmp/dependencies.

    Really, the build should be done with “cabal install”, but I don’t have enough experience to recommend that right now.



    3 Jan 09 at 9:35 pm

  11. Jason,

    And apologies for the hassle. I should be more explicit in my building instructions. I’ll fix that.



    3 Jan 09 at 9:36 pm

  12. Thanks a bunch Alson, looking forward to trying Turbinado out 🙂

    Jason Dew

    3 Jan 09 at 9:40 pm

  13. Hi Jason, I managed to build turbinado after several hours scratching my head, just be sure that you have the correct version of each package, check on turbinado/turbinado.cabal. As Alson said in one post: “Hackage doesn’t necessarily contain the latest and greatest versions of a library”. So, if you used cabal to install your packages, you will finish with loads of broken dependencies.



    4 Jan 09 at 2:55 pm

  14. BTW, once you have the correct packages, you just need to do this:
    cabal configure
    cabal install
    And everything will work!


    4 Jan 09 at 2:59 pm

  15. Adolfo,

    BTW, I added to the turbinado repo all dependencies in tmp/dependencies. Horrible solution, but it’ll have to do until I’m comfortable with cabal-install…

    Happy to hear that you got Turbinado to build. Would love to hear how it’s good, how’s it bad, things to improve, etc!



    4 Jan 09 at 9:16 pm

  16. Alson,
    I started to work on turbinado again just yesterday. My idea is to made a “poor-man’s blog” , Basically the structure is be very similar to “Page.hs”. But I can’t still manage even to save something in the database, and I got some questions .

    -In the views that you show here, you have defined “page” and then the html code, but, in the code of turbinado you have “markup” in the views. With “page” doesn’t work, with “markup” does;, which is the difference between this ones ?

    • In the view “New.hs” you use this in the Form’s action attribute: “(getViewDataValue_u “save-url” :: View String) method=”post””, How that getViewDAtaValue_u work ? After that It is supposed to go to create controller, right ? I can’t understand how do you do that. ;(.

    -There are some methods like that can be easy to understand while another ones not so easy (BTW.How do you call it? a helper ?) ,I saw that a big of this methods that you use are located on the folder Environment, would be cool if in each example that is produced or even in the code, a bit more of explanation of this .

    For now I don’t remember more of my questions, I’ll be in #haskell , maybe see you later there.



    5 Jan 09 at 10:46 am

  17. In the third question this is the method :
    “There are some methods like :”insertDefaultView” …”


    5 Jan 09 at 10:49 am

  18. Adolfo,

    Turbinado is certainly in some flux right now, so I need to update this post with some new function names…

    Page: I decided to rename the function for the View to be “markup” rather than “page” when I created Components. “markup” is more generic and works across Views, Layouts or Components.

    getViewDataValue: this function pulls a value out of the ViewData in the Environment. Check out Turbinado/Environment/ViewData.hs. ViewData is intended to hold bits of data produced by the Controller for consumption by the View. getViewDataValue returns a “Maybe a” and getViewDataValue_u is the unsafe version that returns an “a” (it assumes that the key exists in the ViewData map).

    Documentation: sorely lacking. Been struggling with other bits and with features so need to focus on documentation soon. Have done a bit in http://turbinado.org/Architecture/Show/architecture .

    insertDefaultView: this function uses the Request to figure out what the default should be an inserts it. So /Home/Index would insert the Home/Index.hs:markup view. There’s also an insertView function that allows you to specify a view to insert.

    I’ll try to drop by #haskell a bit later. Thank you for the questions!



    5 Jan 09 at 12:46 pm

Leave a Reply