packages feed

happs-tutorial 0.6.3 → 0.6.4

raw patch · 11 files changed

+201/−172 lines, 11 files

Files

happs-tutorial.cabal view
@@ -1,5 +1,5 @@ Name:                happs-tutorial-Version:             0.6.3+Version:             0.6.4 Synopsis:            A HAppS Tutorial that is is own web 2.0-type demo.  Description:         A nice way to learn how to build web sites with HAppS @@ -23,7 +23,7 @@     static/*.css     templates/*.st -Cabal-Version:       >= 1.2+Cabal-Version:       >= 1.6  Executable happs-tutorial     Main-is:             Main.hs
src/Controller.hs view
@@ -24,7 +24,7 @@ import ControllerMisc import ControllerStressTests -import HAppS.Helpers.DirBrowse+import HAppS.Helpers import Misc  import Data.ByteString.Internal  import Text.StringTemplate.Helpers@@ -55,7 +55,7 @@       ++ [ msgToSp "Quoth this server... 404." ]  -- with diretoryGroupsOld (lazy readFile), appkiller.sh causes crash-getTemplateGroups = directoryGroups "templates" +getTemplateGroups = directoryGroupsHAppS "templates"   tutorial :: STDirGroups String -> Bool -> Bool -> [ServerPartT IO Response] tutorial tDirGroups' dynamicTemplateReload allowStressTests = [ ServerPartT $ \rq -> do@@ -133,3 +133,4 @@   +-- tEmail = runIO $ echo "this is an email" -|- "mailx -s \"O HAI SUBJECT LINE\" thomashartman1@gmail.com"
src/ControllerMisc.hs view
@@ -28,16 +28,15 @@ -- The user argument could be bytestring rather than say, UserName, to keep things generic -- This way we could have different types of users, say UserName users and AdminUserName users. -- But for now, keep UserName.-startsess' ::-  --(HAppS.Server.SURI.ToSURI uri) =>-  (Request -> String) -> RenderGlobals -> UserName -> WebT IO Response-startsess' getLandingpage (RenderGlobals origRq ts _) user = do+startsess' :: (MonadIO m) => RenderGlobals -> UserName -> String -> WebT m Response+startsess' ( RenderGlobals origRq ts _ ) user landingpage = do     let sd = SessionData user   key <- update $ NewSession sd-  addCookie (3600) (mkCookie "sid" (show key))-  let newRGlobs = RenderGlobals origRq ts (Just sd) -  let lp = getLandingpage origRq-  redirectToUrl lp +  addCookie 3600 (mkCookie "sid" (show key))+  return $ RenderGlobals origRq ts (Just sd) +  --let lp = getLandingpage origRq+  --return () +  redirectToUrl landingpage   getLoggedInUserInfos :: RenderGlobals -> ErrorT String (WebT IO) (UserName,UserInfos) getLoggedInUserInfos (RenderGlobals _ _ Nothing) = fail "getLoggedInUserInfos, not logged in"
src/ControllerPostActions.hs view
@@ -1,6 +1,9 @@ {-# LANGUAGE ScopedTypeVariables #-} module ControllerPostActions where +import Debug.Trace.Helpers+import Text.ParserCombinators.Parsec+ import Control.Monad import Control.Monad.Trans import Data.List (isInfixOf)@@ -20,36 +23,55 @@ import ControllerGetActions import FromDataInstances + loginPage :: RenderGlobals -> ServerPartT IO Response-loginPage = loginPage' authUser (startsess' getLoginReferer)+loginPage rglobs@(RenderGlobals rq _ _) = do +  -- unfortunately can't just use rqUrl rq here, because sometimes has port numbers and... it gets complicated+  case tutAppReferrer rq of+    Left e -> errW rglobs e+    Right landingpage -> loginPage' authUser startsess' rglobs landingpage    where      authUser = authUser' getUserPassword-    getUserPassword name = return . maybe Nothing (Just . B.unpack . password)-                                 =<< query (GetUserInfos name)-        -- after login, just redirect to the last page we were on.-    getLoginReferer :: Request -> String-    getLoginReferer rq = -              let homepage = smartAppUrl "" rq -              in case getHeaderVal "referer" rq of -                    Left _ -> homepage-                    Right rf -> if isInfixOf "logout" rf-                      then homepage-                      else rf+    getUserPassword name = return . maybe Nothing (Just . B.unpack . password) =<< query (GetUserInfos name) -loginPage' :: (UserName -> B.ByteString -> WebT IO Bool)-                -> (RenderGlobals -> UserName -> WebT IO Response) -              -> RenderGlobals -> ServerPartT IO Response-loginPage' auth sessW' rglobs =+    ++++-- move this to HAppSHelpers+tutAppReferrer :: Request -> Either String String+tutAppReferrer rq = do+          let approot :: Either String String+              approot = modRewriteAppUrl "" rq +          case approot of +            Left _ -> Left $ "tutAppReferrer, could not determine approot, rq: " ++ (show rq)+            Right ar -> do +              case getHeaderVal "referer" rq of +                  Left e -> Left $ "smartAppReferrer error, rq: " ++ e+                  -- check against logout, otherwise if you have just logged out then +                  -- try immediately to log in again it won't let you.+                  Right rf ->  if isInfixOf "logout" rf || isInfixOf "login" rf || isInfixOf "newuser" rf+                      then Right ar+                      else  Right rf++-- Use a helper function because the plan is to eventually have a similar function+-- that works for admin logins+loginPage' :: (Monad m) =>+              (UserName -> B.ByteString -> WebT m Bool)+              -> (RenderGlobals -> UserName -> String -> WebT m Response)+              -> RenderGlobals+              -> String+              -> ServerPartT m Response+loginPage' auth startsession rglobs landingpage =   withData $ \(UserAuthInfo user pass) -> -    [ ServerPartT $ \_ -> do+    [ ServerPartT $ \rq -> do         loginOk <- auth user pass         if loginOk-          then sessW' rglobs user-          else errW "login error: invalid username or password"-    ]             -  where errW msg = return $ tutlayoutU rglobs [("loginerrormsg",msg)] "home"--+          then startsession rglobs user landingpage+          else errW rglobs "Invalid user or password" {- case ( modRewriteCompatibleTutPath rq ) of+            Left e -> errW rglobs e +            Right p -> return $ tutlayoutU rglobs [("loginerrormsg","login error: invalid username or password")] p -}+    ]  -- check if a username and password is valid. If it is, return the user as successful monadic value -- otherwise fail monadically@@ -78,11 +100,11 @@ processformEditConsultantProfile rglobs =   withData $ \fd@(EditUserProfileFormData fdContact fdBlurb fdlistAsC fdimagecontents) -> [ ServerPartT $ \rq -> do  case (return . sesUser =<< mbSession rglobs) of-   Nothing -> errW "Not logged in"+   Nothing -> errW rglobs "Not logged in"    Just unB -> do      mbUP <- query $ GetUserProfile unB      case mbUP of-       Nothing -> errW "error retrieving user infos"+       Nothing -> errW rglobs "error retrieving user infos"        Just (UserProfile pContact pBlurb listasC pAvatar) -> do          up <- if B.null (fdimagecontents)            then return $ UserProfile fdContact fdBlurb fdlistAsC pAvatar@@ -95,73 +117,54 @@          update $ SetUserProfile unB up          unServerPartT ( viewEditConsultantProfile rglobs) rq   ]-  where errW msg = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate"  processformEditJob :: RenderGlobals -> ServerPartT IO Response processformEditJob rglobs@(RenderGlobals rq ts mbSess) =   withData $ \(EditJob jn jbud jblu) -> [ ServerPartT $ \rq -> do   case (return . sesUser =<< mbSess) of-    Nothing -> errW "Not logged in" +    Nothing -> errW rglobs "Not logged in"      -- Just olduser@(User uname p cp js) -> do     Just uname -> do       if null (B.unpack . unjobname $ jn)-        then errW "error, blank job name"+        then errW rglobs "error, blank job name"         else do            update $ SetJob uname jn (Job (B.pack jbud) (B.pack jblu))           unServerPartT ( viewEditJob uname jn rglobs) rq     ]-   where errW msg = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate"++errW rglobs msg = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate"                 processformNewJob rglobs@(RenderGlobals rq ts mbSess) =   withData $ \(NewJobFormData jn newjob) -> [ ServerPartT $ \rq -> do  case (return . sesUser =<< mbSess) of-   Nothing -> errW "Not logged in"+   Nothing -> errW rglobs "Not logged in"    Just user -> do      if null (B.unpack . unjobname $ jn)-        then errW "error, blank job name"+        then errW rglobs "error, blank job name"         else do           res <- update (AddJob user jn newjob)          case res of             Left err -> case isInfixOf "duplicate key" (lc err) of-                              True -> errW "duplicate job name"-                              otherwise -> errW "error inserting job"+                              True -> errW rglobs "duplicate job name"+                              otherwise -> errW rglobs "error inserting job"            Right () -> unServerPartT ( pageMyJobPosts rglobs ) rq    ]-  where errW msg = ( return . tutlayoutU rglobs [("errormsg", msg)] ) "errortemplate" -{--newUserPage :: RenderGlobals -> ServerPartT IO Response-newUserPage rglobs =-  withData $ \(NewUserInfo user pass1 pass2) -> -    [ ServerPartT $ \_ ->-         do userExists <- query $ IsUser user-            newuser user pass1 pass2 userExists-    ]-    where errW msg = ( return . tutlayoutU rglobs [("errormsgRegister", msg)] ) "register" -          newuser :: UserName -> B.ByteString -> B.ByteString -> Bool -> WebT IO Response-          newuser u@(UserName us) pass1 pass2 userExists-              | pass1 /= pass2 = errW "passwords did not match"-              | null . B.unpack $ pass1 = errW "bad password"-              | null . B.unpack $ us = errW "bad username"-              | userExists = errW "User already exists"-              | otherwise =  do update $ AddUser u $ scramblepass pass1-                                mbUis <- query $ GetUserInfos u-                                case mbUis of-                                  Nothing -> errW "newUserPage, update failed"-                                  Just uis -> startsess rglobs u--}  newUserPage :: RenderGlobals -> ServerPartT IO Response newUserPage rglobs =   withData $ \(NewUserInfo user (pass1 :: B.ByteString) pass2) ->     [ ServerPartT $ \rq -> do-  etRes <- runErrorT $ do-    setupNewUser (NewUserInfo user (pass1 :: B.ByteString) pass2) -  case etRes of-    Left err -> return $ tutlayoutU rglobs [("errormsgRegister", err)] "register"-    Right () -> startsess' (smartAppUrl "tutorial/registered") rglobs user-        ]+      etRes <- runErrorT $ do+        setupNewUser (NewUserInfo user (pass1 :: B.ByteString) pass2) +      case etRes of+          Left err -> errW rglobs err +          Right () -> do case modRewriteAppUrl "tutorial/registered" rq of+                           Left e -> errW rglobs e+                           Right p -> startsess' rglobs user p+                         +            ]   where     setupNewUser :: NewUserInfo -> ErrorT [Char] (WebT IO) ()     setupNewUser (NewUserInfo user (pass1 :: B.ByteString) pass2) = do@@ -179,6 +182,8 @@           else return ()         addUserVerifiedPass user pass1 pass2 +  + addUserVerifiedPass :: UserName -> B.ByteString -> B.ByteString -> ErrorT String (WebT IO) () addUserVerifiedPass user pass1 pass2 = do   ErrorT $ newuser user pass1 pass2@@ -187,4 +192,6 @@     newuser u@(UserName us) pass1 pass2 -- userExists       | pass1 /= pass2 = return . Left $  "passwords did not match"       | otherwise = update $ AddUser u $ B.pack $ scramblepass (B.unpack pass1)++ 
src/View.hs view
@@ -2,9 +2,6 @@  import Text.StringTemplate import Misc--- use directoryGroupSafer instead,--- otherwise there are annoyances with punctuation containing emacs backup files and the like--- import Text.StringTemplate hiding (directoryGroup)  import qualified Data.Map as M import Data.List import HAppS.Server.HTTP.Types (rqURL, Request)
templates/gettingstarted.st view
@@ -1,16 +1,5 @@ <h4>Getting started with HAppS</h4> -<p>If you use Ruby on Rails, Django, Perl Catalyst, PHP, or some other popular web framework, but-have programmed in haskell and would like to use the world's greatest language for your next web project,-just keep reading. I promise by the time you're done you  will give you all the knowledge you need.</p>--<p>This tutorial is its own demo. Both the job board part, and the text you are reading this moment.-For best results, you should-<a href="/tutorial/run-tutorial-locally">install and run</a>-it in a local environment where you have control. Then, when you are done-learning, you can use the tutorial code as a starting point for your own-HAppS applications. -</p>  <p>I'm going to repeat myself now. The best way to learn how to use use HAppS with this tutorial is to <a href="/tutorial/run-tutorial-locally">install and run</a> it in a
templates/home.st view
@@ -1,64 +1,24 @@-<h3>Real World HAppS</h3>--$!<p>Haskell is a great way to program.</p>!$+<h3>Real World HAppS: building a Web 2.0 App with haskell</h3>  <p><a href="http://www.happs.org">HAppS</a> is a great way to build web applications.      Besides having a great feature set in its own right, it is probably the leading solution    for implementing web apps in <a href="http://www.google.com/search?hl=en&q=why+haskell">haskell</a>,     my favorite language. -$!<p>You also get all the <a href="http://www.google.com/search?hl=en&q=why+haskell">goodness</a>-that comes from programming in <a href="http://www.haskell.org">haskell</a>, my favorite language.</p>-!$--<p>HAppS is especially great if you believe, like I do, that as-modern software systems tend toward ever increasing complexity,-database usage is an unnecessary source of complication that-<a href=http://gilesbowkett.blogspot.com/2007/05/sql-unnecessary-in-haskells-happs.html>should be factored out</a> where possible.--<p>Ruby's <a href="http://rubyonrails.com/">rails</a> and python's <a href="http://www.djangoproject.com">django</a> have become popular largely because of their <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">object relational mapping</a> systems,-which hide the complexity of database engines by converting application data manipulation logic into sql.-When I first used an ORM, it felt like a huge improvement over writing sql statements every time I wanted to manipulate-an application's state. But pretty soon ORMs started seeming hackish to me too. At some point,-the metaphors I wanted to use just <a href="http://en.wikipedia.org/wiki/Object-Relational_impedance_mismatch">broke down</a>.---$!Or to put it another way, that <a-href=http://gilesbowkett.blogspot.com/2007/05/sql-unnecessary-in-haskells-happs.html>sql-is an ugly hack</a>. !$-<p> HAppS is haskell's answer to rails and django (and perl's <a href="http://www.catalystframework.org/">catalyst</a>, -    and <a href="http://www.php.net">php</a>).-    $! , and every ORM ever written in the history of software) !$ -    With HAppS, there is no wrangling data-    structures into and out of the database, because there is no database.  You use whatever data-    structures are natural to your application, and serialize them-    transparently using-    <a href="http://www.google.com/search?q=scrap+your+boilerplate">powerful</a>-    machinery</a> that's running <a href="http://www.haskell.org/th/">behind the-    scenes</a>. And if there are existing databases that you need to connect to, you can do that too -    -- you're not locked in to using macid for everything.--<p><a href="http://hackage.haskell.org/packages/archive/HAppS-State/0.9.2.1/doc/html/HAppS-State.html#1">MACID</a>,-the HAppS storage mechanism, is no vanilla serialization layer that will-start acting in weird ways when an application has many concurrent users doing possibly conflicting things. -By <a href="http://research.microsoft.com/~simonpj/papers/stm/">leveraging haskell's type system</a> -(see composable memory transactions paper),-you get the same <a href="http://en.wikipedia.org/wiki/ACID">ACID</a> guarantees that-normally only come with a database.  </p>---<p>There are some <a href="/tutorial/macid-stress-test">limitations</a> to using macid -   as a datastore that you should familiarize yourself with-   if you are looking into using HAppS for heavy-usage transactional applications.-   But long term, HAppS with macid looks promising enough that I've started-   using it as a platform for building commercial web 2.0 type apps. (My first -   commercial happs app will be public soon, so stay tuned on -   <a href=http://www.techcrunch.com>techcrunch</a>. &nbsp; :) &nbsp; )--<p>In short, HAppS is awesome, and webmonkeys everywhere should use it. Except...--<p>There is this one <a href="/tutorial/missing-happs-documentation">minor detail</a>.-+<p>If you use Ruby on Rails, Django, Perl Catalyst, PHP, or some other popular web framework, but+have programmed in haskell and would like to use the world's greatest language for your next web project,+just keep reading. I promise by the time you're done you  will posess all the knowledge +and sample code you need.</p> +<p>This tutorial is its own demo, and it is <a href="http://hackage.haskell.org/cgi-bin/hackage-scripts/package/happs-tutorial">open source</a>. The tutorial explains how to build a toy job board, which you can try out for yourself by+creating a user on this demo site, and play with locally after you have installed the source code.+The text you are reading this moment is bundled up too, hence "self-demoing tutorial."+For best results, you should+<a href="/tutorial/run-tutorial-locally">install and run</a>+it in a local environment where you have control. Then, when you are done+learning, you can use the tutorial code as a starting point for your own+HAppS applications. +</p> +<p><a href="/tutorial/why-happs-is-cool">Why HAppS?</a></p> 
templates/missinghappsdocumentation.st view
@@ -1,33 +1,31 @@ <h3>The missing HAppS documentation, and what to do about it</h3> -<p>Unfortunately, the documentation for HAppS is+<p>When I started building this demo/tutorial, the documentation for HAppS was <a href="http://forums.somethingawful.com/showthread.php?threadid=2947408">cringeworthy</a>.-So  bad that honestly I wouldn't know where to start to fix it.+So  bad that honestly I wasn't sure where to start with trying to fix it. </p>  <p> Instead of tackling the documentation probablem directly by doing the obvious thing and writing patches for haddock, I decided to create an easy-to-install demo project-that included clear step-by-step instructions for getting from zero to final product. I figure what the HAppS+that included clear step-by-step instructions for getting from zero to final product. +There is still a lot of work to be done in the documentation and usability department.+That haddock still needs to be written, and more than anything, HAppS needs to be used and discussed+by more people in forums, blogs, wikis, and real applications like <a href="hackage.haskell.org">hackage</a> +(which I think is already using HAppS or will be soon). +These things tend to snowball. My hope is that this tutorial will play a part in bringing +commercial-use HAppS and haskell into the real world. ++<p>+I figured what the HAppS ecosystem needs most, after better documentation, is the opportunity for developers to make real money-doing real work by leveraging this technology. So the demo project is a job board.+doing real work by leveraging this technology. ++<p>In this spirit, the demo project is a job board. It's full of dummy data at the moment, but feel free to post real jobs here if you have them on offer. (But cc the happs googlegroup as well! &nbsp; :) &nbsp; )-$!-Somewhat to my shame, I still haven't learned how HAppS data migrations work,-so every time I release a new version of the tutorial I lose all data -- however, if any real seeming jobs-get posted, I will make sure this doesn't happen. A good explanation of HAppS data migration is my top priority-for the next version anyway. !$ --$! There is a real job board, based on code from this tutorial, at <a href="http://www.fpjobs.com">fpjobs.com</a>. !$- <p>-By the way,-<a href="/tutorial/viewprofile?user=tphyahoo">I am currently available for haskell, HAppS, and startup consulting</a>. -</p>--<p> My hope is that that this material will tempt people to try HAppS out. Both curious developers, and investors that want a non-trivial website and are willing to <a href="http://www.paulgraham.com/avg.html">gamble</a> on a new technology@@ -35,11 +33,10 @@ With enough users, I believe the documentation and other "batteries not included" issues with HAppS will matter less and less, as the gaps get plugged in a gradual way. -$!-I created this project, the Real -World HAppS Tutorial, to popularize the use of haskell, with HAppS, in web applications. -!$-</p> +<p>+On this note, I am currently available for haskell, HAppS, and startup consulting: thomashartman1 at gmail. +Write me if there is anything you need.+</p> -<p><a href="/tutorial/getting-started">Let's get started.</a></p>+<p><a href="/tutorial/getting-started">Now let's get started.</a></p> 
templates/runtutoriallocally.st view
@@ -5,19 +5,33 @@ <p>This tutorial is cabalized. You can install it, and chase down all the HAppS dependencies it needs, simply by doing  <p>cabal install happs-tutorial -<p>The cabal installation may take up to an hour, mainly because the-HAppS-Server installation is slow, but it should succeed in one-shot. This is a symptom of the <a href="/tutorial/happs-slow-linking-bug">HAppS slow linking bug</a>.+<p>The cabal installation is kind of slow. I've had it take up to 15 minutes. But it should succeed in one+shot. (This is a symptom of the <a href="/tutorial/happs-slow-linking-bug">HAppS slow linking bug</a>.) +<p><font color=orange>Unfortunately, the above statement is false on ghc 6.10.1 at the moment, because +   <br>the crypto package won't cabal install out of the box. You can build crypto first by doing +   <br>darcs get http://code.haskell.org/crypto+   <br>cd crypto+   <br>cabal install+   <br>Hopefully the crypto package will get a version bump on hackage soon so  I can delete this ugly orange paragraph.</font>+ <p>If you've never used cabal install or need more detailed info....</p>  <ul>-    <li>Haskell: You need at least ghc 6.8.2 to install HAppS. I installed this with with apt-get install haskell (works for ubuntu hardy heron), and then <a href="http://www.haskell.org/ghc/download.html">upgraded to ghc 6.8.3</a> as this is supposed to have fixed some bugs.</li> -    <li>Dependency chasing haskell package installers: you should have the latest versions of cabal and cabal install from <a href="http://hackage.haskell.org/packages/archive/pkg-list.html">hackage</a>. These are already included in the latest version of ghc, or they will be soon. Another reason to upgrade to ghc 6.8.3.</li>+    <li>Haskell: I use ghc 6.10.1, and suggest you do too. +    I installed this with with apt-get install haskell (works for ubuntu hardy heron), and then <a href="http://www.haskell.org/ghc/download.html">upgraded to ghc 6.10.1</a>.</li> +    <li>Dependency chasing haskell package installers: you should have the latest versions of cabal and cabal install from <a href="http://hackage.haskell.org/packages/archive/pkg-list.html">hackage</a>. These are already included in the latest version of ghc, or they will be soon. I have +   <br>cabal --version+   <br>cabal-install version 0.6.0+   <br>using version 1.6.0.1 of the Cabal library +   <br>The latest version of cabal is a bit tricky to install. What I did was start with ghc 6.10.1 and then+   download the tar file of the latest version of cabal from hackage, unzip that and run bootstrap.sh.++</li>     <li>If you want to check out the latest version of this tutorial, install <a href="http://www.darcs.net">Darcs</a> and check out the repo with darcs get http://code.haskell.org/happs-tutorial</li> </ul> -<p>I cabalized happs-tutorial was for the dependency chasing you get with cabal install,+<p>The reason I cabalized happs-tutorial was for the dependency chasing you get with cabal install, not for actually running it.  @@ -50,6 +64,9 @@ *** Exception: _local/happs-tutorial_state/events-0000000006: openFile: resource busy (file is locked)  <p>Don't worry about it. Every time I get this error I simply run runInGhci again, and the second time it always works.++<p>As far as I know, this issue does not occur when you run the tutorial from a compiled executable, which is of course+  how you should be running for a production application.  <p> You may also want to <a href="start-happs-on-boot">start HAppS on boot</a>.
templates/toc.st view
@@ -1,4 +1,5 @@-[("/tutorial/home","happs intro")+[("/tutorial/home","build a web 2.0 app in happs")+ , ("/tutorial/why-happs-is-cool","why happs is cool")  , ("/tutorial/missing-happs-documentation","the missing happs documentation")  , ("/tutorial/getting-started","getting started with happs")  , ("/tutorial/prerequisites","prerequisites")
+ templates/whyhappsiscool.st view
@@ -0,0 +1,61 @@+<h3>Look Ma, No Database</h3>++<p>There are a lot of advantages to programming in a typed functional language like haskell.+   Certain bugs, like misuse of global variables, are virtually impossible unless you bend over backwards+   to do things wrong. Code tends to be incredibly short, and modular. The haskell community is very +   friendly, and with coders in every time zone the #haskell irc channel seems well populated +   seemingly 24 hours a day.++<p>But we're here to talk about HAppS, not haskell.+   +<p>What got me interested in HAppS was a strongly held feeling that as+modern software systems tend toward ever increasing complexity,+database usage is an unnecessary source of complication that+<a href=http://gilesbowkett.blogspot.com/2007/05/sql-unnecessary-in-haskells-happs.html>should be factored out</a> where possible.++<p>Ruby's <a href="http://rubyonrails.com/">rails</a> and python's <a href="http://www.djangoproject.com">django</a> have become popular largely because of their <a href="http://en.wikipedia.org/wiki/Object-relational_mapping">object relational mapping</a> systems,+which hide the complexity of database engines by converting application data manipulation logic into sql.+When I first used an ORM, it felt like a huge improvement over writing sql statements every time I wanted to manipulate+an application's state. But pretty soon ORMs started seeming hackish to me too. At some point,+the metaphors I wanted to use just <a href="http://en.wikipedia.org/wiki/Object-Relational_impedance_mismatch">broke down</a>.+++$!Or to put it another way, that <a+href=http://gilesbowkett.blogspot.com/2007/05/sql-unnecessary-in-haskells-happs.html>sql+is an ugly hack</a>. !$+<p> HAppS is haskell's answer to rails and django (and perl's <a href="http://www.catalystframework.org/">catalyst</a>, +    and <a href="http://www.php.net">php</a>).++    With HAppS, there is no wrangling data+    structures into and out of the database, because there is no database.  You use whatever data+    structures are natural to your application, and serialize them+    transparently using+    <a href="http://www.google.com/search?q=scrap+your+boilerplate">powerful</a>+    machinery</a> that's running <a href="http://www.haskell.org/th/">behind the+    scenes</a>. And if there are existing databases that you need to connect to, you can do that too +    -- you're not locked in to using macid for everything.++<p><a href="http://hackage.haskell.org/packages/archive/HAppS-State/0.9.2.1/doc/html/HAppS-State.html#1">MACID</a>,+the HAppS storage mechanism, is no vanilla serialization layer that will+start acting in weird ways when an application has many concurrent users doing possibly conflicting things. +By <a href="http://research.microsoft.com/~simonpj/papers/stm/">leveraging haskell's type system</a> +(see composable memory transactions paper),+you get the same <a href="http://en.wikipedia.org/wiki/ACID">ACID</a> guarantees that+normally only come with a database.  </p>+++<p>There are some <a href="/tutorial/macid-stress-test">limitations</a> to using macid +   as a datastore that you should familiarize yourself with+   if you are looking into using HAppS for heavy-usage transactional applications.+   But long term, HAppS with macid looks promising enough that I've started+   using it as a platform for building commercial web 2.0 type apps. (My first +   commercial happs app will be public soon, so stay tuned on +   <a href=http://www.techcrunch.com>techcrunch</a>. &nbsp; :) &nbsp; )++<p>In short, HAppS is awesome, and webmonkeys everywhere should use it. Except...++<p>Well, nothing is <a href="/tutorial/missing-happs-documentation">perfect</a>.++++