I have some snaplet like this:
data DB b = DB
{_pgsql :: Snaplet Postgresql
,dbCache :: Map Text Text
}
And I wish to fill dbCache
from postgresql database. Seems that it is natural to do this during snaplet initialization.
initDB :: SnapletInit b (DB b)
initDB = makeSnaplet "db" "cached database" Nothing $ do
pgs <- nestSnaplet "pgsql" pgsql pgsInit
cache <- getSomeDataPlease pgs
return $ DB pgs cache
So, the question is: How is it possible to use pgs :: Snaplet Postgres
within Initializer
monad to read data from db?
The DB access functions provided by snaplet-postgresql-simple run in any monad that is an instance of the HasPostgres
type class. Typically, this will be the Handler
monad for your application.
You can't use Handler
functions inside an Initializer
. The whole point of the Initializer monad is to set up the initial state data type that is needed to run the web server and the Handler monad. So it's truly impossible to run handlers inside an initializer--unless of course you're running one web server from inside another web server...ick.
So you have two possible options. You could create a HasPostgres
instance for your Initializer
. But that doesn't make much sense unless you're connecting to a static server. This might be acceptable if you're doing debugging. Sometimes I'll do that for IO to make it trivial to test my database functions:
instance HasPostgres IO where
getPostgresState = do
pool <- createPool (connect $ ConnectInfo "127.0.0.1" ...) ...
return $ Postgres pool
But in general it won't make sense to make an instance like this for use in production code. This means that if you want to access the database in an Initializer
you have to use the postgresql-simple functions directly rather than the wrappers provided by snaplet-postgresql-simple. That's why I exported the pgPool accessor function. It will look something like this:
initDB :: SnapletInit b (DB b)
initDB = makeSnaplet "db" "cached database" Nothing $ do
pgs <- nestSnaplet "pgsql" pgsql pgsInit
let pool = pgPool $ extract pgs
results <- liftIO $ withResource pool (\conn -> query_ conn myQuery)
You can see a real live example of this in snaplet-postgresql-simple's auth backend.
Update:
I just uploaded a new version of snaplet-postgresql-simple to hackage that provides a HasPostgres instance for ReaderT. This allows you to accomplish this more simply with runReaderT. There's a small code snippet of this in the documentation.