Persistent: CRUD TypeClass
crud, haskell, persistent, yesod
Solution
The constraint means that the associated backend type for a `PersistEntity` instance needs to be `SqlBackend`, so when a user implements the `PersistEntity` class as part of implementing the `CRUD` class they will need to specify that.
From your point of view, you just need to enable the `TypeFamilies` extension and add that constraint to your class definition:
class ( J.FromJSON a, J.ToJSON a, SQL.PersistEntity a
, SQL.PersistEntityBackend a ~ SQL.SqlBackend
) => CRUD a where
...
When defining an instance of `PersistEntity` for some type `Foo`, the user of `CRUD` will need to define the `PersistEntityBackend` type to be `SqlBackend`:
instance PersistEntity Foo where
type PersistEntityBackend Foo = SqlBackend
Here's my complete copy of your code that passes the GHC type-checker:
{-# LANGUAGE TypeFamilies #-}
import Control.Monad.Logger
import Control.Monad.Trans
import qualified Data.Aeson as J
import Data.Conduit
import Data.String ( fromString )
import qualified Database.Persist.Sql as SQL
import Web.Scotty
-- incomplete definition, not sure why this instance is now needed
-- but it's not related to your problem
instance MonadLogger IO
-- I can't build persistent-mysql on Windows so I replaced it with a stub
runDB x = liftIO $ runResourceT $ SQL.withSqlConn undefined $ SQL.runSqlConn x
class ( J.FromJSON a, J.ToJSON a, SQL.PersistEntity a
, SQL.PersistEntityBackend a ~ SQL.SqlBackend
) => CRUD a where
getBasePath :: a -> String
getCrudName :: a -> String
getFromBody :: a -> ActionM a
getFromBody _ = do body <- jsonData
return body
mkInsertRoute :: a -> ScottyM ()
mkInsertRoute el =
do post (fromString ((getBasePath el) ++ "/" ++ (getCrudName el))) $ do
body <- getFromBody el
runDB $ SQL.insert body
json $ J.Bool True
mkUpdateRoute :: a -> ScottyM ()
mkDeleteRoute :: a -> ScottyM ()
mkGetRoute :: a -> ScottyM ()
mkGetAllRoute :: a -> ScottyM ()
Problem
I am trying to write a typeclass that simplifies writing a CRUD backend using persistent, aeson and scotty Here is my idea: ``` runDB x = liftIO $ do info <- mysqlInfo runResourceT $ SQL.withMySQLConn info $ SQL.runSqlConn x class (J.FromJSON a, J.ToJSON a, SQL.PersistEntity a) => CRUD a where getBasePath :: a -> String getCrudName :: a -> String getFromBody :: a -> ActionM a getFromBody _ = do body <- jsonData return body mkInsertRoute :: a -> ScottyM () mkInsertRoute el = do post (fromString ((getBasePath el) ++ "/" ++ (getCrudName el))) $ do body <- getFromBody el runDB $ SQL.insert body json $ J.Bool True mkUpdateRoute :: a -> ScottyM () mkDeleteRoute :: a -> ScottyM () mkGetRoute :: a -> ScottyM () mkGetAllRoute :: a -> ScottyM () ``` This doesn't compile, I get this error: ``` Could not deduce (SQL.PersistEntityBackend a ~ Database.Persist.GenericSql.Raw.SqlBackend) from the context (CRUD a) bound by the class declaration for `CRUD' at WebIf/CRUD.hs:(18,1)-(36,36) Expected type: SQL.PersistEntityBackend a Actual type: SQL.PersistMonadBackend (SQL.SqlPersist (Control.Monad.Trans.Resource.ResourceT IO)) In the second argument of `($)', namely `SQL.insert body' In a stmt of a 'do' block: runDB $ SQL.insert body In the second argument of `($)', namely `do { body <- getFromBody el; runDB $ SQL.insert body; json $ J.Bool True }' ``` It seems like I have to add another type-constraint, something like `PersistMonadBackend m ~ PersistEntityBackend a`, but I don't see how.