Search code examples
haskellpersistent

Automatically generating `PersistEntity` for pre-existing types


Motivation: I want to use MongoDB to store data. The persistent library seems to be the only high level Haskell library supporting MongoDB. My project has already defined types representing the rows (documents) of any database.

Typical use of persistent is to define your type via a bit of template Haskell, as such:

{-# LANGUAGE GADTs                      #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE QuasiQuotes                #-}
{-# LANGUAGE TemplateHaskell            #-}
{-# LANGUAGE TypeFamilies               #-}
import Database.Persist
import Database.Persist.TH
import Database.Persist.Sqlite
import Control.Monad.IO.Class (liftIO)

mkPersist mongoSettings [persistLowerCase|
Person
    name String
    age Int
    deriving Show
|]

However, I already have significant sized types in code akin to:

newtype Name = Name String deriving (Show, Etc, Etc)
data Person = Person
                 { name :: Name, age :: Int } deriving (Show, Etc, Etc)

So ideally I'd get my PersistEntity and maybe even PersistField instances via a slimmed down bit of TH such as:

mkPersistFromType mongoSettings ''Person

However, there is no TH function like mkPersistFromType. Writing the class instances by hand is tedious - they are extremely long. What is the right way forward? Is there a mkPersistFromType somewhere I haven't seen or should I just write that myself?


Solution

  • Note that mkPersist is just a function, and it returns a list of declarations to be added to the source file. So you are free to post-process this declarations and e.g. remove unwanted ones.

    Here is an example where I filter out all data declarations:

    myMkPersist settings = do
      filter wanted <$> mkPersist settings [persistLowerCase|
        Person
          name String
        |]
      where
      wanted DataD{} = False
      wanted _ = True
    

    Note that myMkPersist should be defined in a separate file due to the stage restriction. In the main file you use this function:

    data Person = Person
      { personName :: String
      }
    
    myMkPersist mongoSettings
    
    

    Also you may want to check the output on the mkPersist to see how exactly you want to post-process the declarations, you can do that using -ddump-splices cli option.