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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With