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.

Original source