The code so far has ended up with a six or so repeated blocks that look like:
liftIO $ bracket (PG.connectPostgreSQL "user='postgres'") PG.close \conn -> _something_to_do_with_the_database_
More than three times violates the Rule of Three so I'm going to pull this out into a new file, src/DB.hs
.
{-# Language OverloadedStrings #-} module DB where import Control.Exception (bracket) import Control.Monad.IO.Class (liftIO, MonadIO) import qualified Database.PostgreSQL.Simple as PG withDB :: MonadIO io => (PG.Connection -> IO b) -> io b withDB act = liftIO $ bracket (PG.connectPostgreSQL "user='postgres'") PG.close act
Now every occurence of bracket
happens to fit into the above pattern, so every place in the code you see this:
[registration] <- liftIO $ bracket (PG.connectPostgreSQL "user='postgres'") PG.close $ \conn -> do PGS.gselectFrom conn "registration where nonce = ?" [identifier]
... replace it with this:
[registration] <- withDB $ \conn -> do PGS.gselectFrom conn "registration where nonce = ?" [identifier]
... which is three lines shorter.
We can go a bit further - this occurs three times:
withDB $ \conn -> PGS.gselectFrom conn "registration where nonce = ?" [identifier]
so replace that with a new selectByNonce
:
import qualified Database.PostgreSQL.Simple.SOP as PGS selectByNonce :: MonadIO io => String -> io [Registration] selectByNonce identifier = withDB $ \conn -> PGS.gselectFrom conn "registration where nonce = ?" [identifier]
... or go one step further and realise that a selectByNonce
is always used where exactly one row is expected to match, like this:
[registration] <- selectByNonce identifier
and so that pattern matching can move into selectByNonce
too, with better error reporting. Now the function will return exactly one Registration
or otherwise throw an error (rather than returning a list and the same error manifesting as a pattern match failure).
selectByNonce :: MonadIO io => String -> io Registration selectByNonce identifier = do res <- withDB $ \conn -> PGS.gselectFrom conn "registration where nonce = ?" [identifier] case res of [r] -> return r [] -> error $ "selectByNonce: no rows returned for " ++ identifier _ -> error $ "selectByNonce: multiple rows returned for " ++ identifier
... with invocations looking like this:
registration <- selectByNonce identifier
Those are just a couple of small changes but they make database access look much tidier. Specifically if the code moved to some more elaborate database connection handling (for example, a persistent connection shared between requests), then withDB
could be rewritten to deal with that but the higher level code would not need to change.
No comments:
Post a Comment