Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell, Snap: Simple snaplet construction. When do we use snaplet and when library?

I am trying to understand simple snaplet construction. Also, when do I actually need to make a snaplet and when a simple side library? And if I do need one how do I make it out of a library?

For example, I have a bunch of DB functions where I wrap my SQL code like below.

data Person = Person {personName :: ByteString, personAge :: Int}

connect :: IO Connection
connect = connectSqlite3 "/somepath/db.sqlite3"

savePerson :: Person -> IO ()
savePerson p = do
c <- connect
run c "INSERT INTO persons (name, age) \
      \VALUES (?, ?)"
      [toSql (personName p), toSql (personAge p)]
commit c
disconnect c

Every function initiates a new connection and closes the connection after commit. I guess making a snaplet is the way to avoid connection in every function? In my handler I would use it like this:

insertPerson :: Handler App App ()
insertPerson = do
  par <- getPostParams
  let p = top par
  liftIO $ savePerson p
where
  top m =
    Person {personName = head (m ! (B.pack "name"))
           ,personAge  = read (B.unpack (head (m ! (B.pack "age")))) :: Int
           }

It works so far. My question(s) is/are: When do I actually need to turn a library into a snaplet? Do I need to turn my simple DB library into a snaplet just to initialize the connection instead of making the connection in every function?

Now, If I make the snaplet ... On the Snap website there is a tiny example of a top level sanaplet but there is no trace of how to make simple pluggble snaplet of your own.

So I added snaplet initialization function to my DB library

dbInit :: SnapletInit b Connection
dbInit = makeSnaplet "DB" "My DB Snaplet" Nothing $ do
  dbc <- liftIO $ connectSqlite3 "/somepath/db.sqlite3"
  onUnload $ disconnect dbc
  return $ dbc

Is this the correct way of doing it? Is this all I need to turn it into a pluggble snaplet?

Then I stack this DB snaplet into the main app

data App = App
  { _heist :: Snaplet (Heist App),
    _dbcon :: Snaplet (Connection)
  }

makeLens ''App

app :: SnapletInit App App
app = makeSnaplet "app" "My app" Nothing $ do
  h <- nestSnaplet "heist" heist $ heistInit "templates"
  d <- nestSnaplet "" dbcon dbInit
  addRoutes routes
  return $ App h d

Now, all I gain is the connection available to my request handlers, right? So my handler becomes:

insertPerson :: Handler App App ()
insertPerson = do
  par <- getPostParams
  let person = top par
  connection <- gets _dbcon
  liftIO $ savePerson connection person
where
  top m =
    Person {personName = head (m ! (B.pack "name"))
           ,personAge  = read (B.unpack (head (m ! (B.pack "age")))) :: Int
           }

This does not seem to work. What am I doing wrong? Is this the correct way to extract the connection from the snaplet handle (dbcon)? Is this generaly the correct direction to construct a simple snaplet? Do I actually need a snaplet here in my case?

Thanks.

like image 334
r.sendecky Avatar asked Mar 30 '12 02:03

r.sendecky


1 Answers

Handler is an instance of MonadState: MonadState v (Handler b v).

Handler is also an instance of MonadSnaplet and therefore provides the with method:
with :: Lens v (Snaplet v') -> m b v' a -> m b v a

dbcon is a Lens App (Snaplet Connection).

So to get to the Connection we can use:
conn <- with dbcon get

You would normally create a snaplet if you were providing functionality that everyone might benefit from. In your case, it's probably best to take advantage of the HDBC snaplet, which you can use to connect to a sqlite3 db.

Checkout http://norm2782.github.com/snaplet-hdbc.html for a good tutorial on using the HDBC snaplet.

like image 69
qubital Avatar answered Sep 18 '22 15:09

qubital